From 42ab839dd6c8a1570b232101eb97f4e54de57935 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Wed, 9 Feb 2011 01:09:01 +0000 Subject: AUDIO: Rename sound/ dir to audio/ svn-id: r55850 --- Makefile.common | 4 +- audio/audiostream.cpp | 407 ++++ audio/audiostream.h | 371 ++++ audio/decoders/adpcm.cpp | 851 ++++++++ audio/decoders/adpcm.h | 90 + audio/decoders/aiff.cpp | 186 ++ audio/decoders/aiff.h | 71 + audio/decoders/flac.cpp | 745 +++++++ audio/decoders/flac.h | 75 + audio/decoders/iff_sound.cpp | 130 ++ audio/decoders/iff_sound.h | 47 + audio/decoders/mac_snd.cpp | 116 ++ audio/decoders/mac_snd.h | 58 + audio/decoders/mp3.cpp | 375 ++++ audio/decoders/mp3.h | 76 + audio/decoders/raw.cpp | 356 ++++ audio/decoders/raw.h | 153 ++ audio/decoders/vag.cpp | 150 ++ audio/decoders/vag.h | 60 + audio/decoders/voc.cpp | 403 ++++ audio/decoders/voc.h | 107 + audio/decoders/vorbis.cpp | 262 +++ audio/decoders/vorbis.h | 75 + audio/decoders/wave.cpp | 194 ++ audio/decoders/wave.h | 84 + audio/fmopl.cpp | 197 ++ audio/fmopl.h | 179 ++ audio/mididrv.cpp | 326 +++ audio/mididrv.h | 288 +++ audio/midiparser.cpp | 467 +++++ audio/midiparser.h | 404 ++++ audio/midiparser_smf.cpp | 384 ++++ audio/midiparser_xmidi.cpp | 375 ++++ audio/mixer.cpp | 556 ++++++ audio/mixer.h | 265 +++ audio/mixer_intern.h | 135 ++ audio/mods/infogrames.cpp | 470 +++++ audio/mods/infogrames.h | 148 ++ audio/mods/maxtrax.cpp | 1040 ++++++++++ audio/mods/maxtrax.h | 225 +++ audio/mods/module.cpp | 252 +++ audio/mods/module.h | 90 + audio/mods/paula.cpp | 212 ++ audio/mods/paula.h | 210 ++ audio/mods/protracker.cpp | 466 +++++ audio/mods/protracker.h | 57 + audio/mods/rjp1.cpp | 582 ++++++ audio/mods/rjp1.h | 50 + audio/mods/soundfx.cpp | 275 +++ audio/mods/soundfx.h | 53 + audio/mods/tfmx.cpp | 1193 +++++++++++ audio/mods/tfmx.h | 284 +++ audio/module.mk | 61 + audio/mpu401.cpp | 145 ++ audio/mpu401.h | 92 + audio/musicplugin.cpp | 64 + audio/musicplugin.h | 125 ++ audio/null.cpp | 62 + audio/null.h | 56 + audio/rate.cpp | 358 ++++ audio/rate.h | 90 + audio/rate_arm.cpp | 465 +++++ audio/rate_arm_asm.s | 687 +++++++ audio/softsynth/adlib.cpp | 1617 +++++++++++++++ audio/softsynth/appleiigs.cpp | 57 + audio/softsynth/cms.cpp | 376 ++++ audio/softsynth/cms.h | 92 + audio/softsynth/emumidi.h | 116 ++ audio/softsynth/fluidsynth.cpp | 254 +++ audio/softsynth/fmtowns_pc98/towns_audio.cpp | 1583 +++++++++++++++ audio/softsynth/fmtowns_pc98/towns_audio.h | 179 ++ audio/softsynth/fmtowns_pc98/towns_euphony.cpp | 905 +++++++++ audio/softsynth/fmtowns_pc98/towns_euphony.h | 187 ++ audio/softsynth/fmtowns_pc98/towns_pc98_driver.cpp | 1428 +++++++++++++ audio/softsynth/fmtowns_pc98/towns_pc98_driver.h | 135 ++ .../softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp | 1548 +++++++++++++++ audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h | 196 ++ audio/softsynth/mt32.cpp | 573 ++++++ audio/softsynth/mt32/freeverb.cpp | 310 +++ audio/softsynth/mt32/freeverb.h | 244 +++ audio/softsynth/mt32/i386.cpp | 849 ++++++++ audio/softsynth/mt32/i386.h | 49 + audio/softsynth/mt32/module.mk | 14 + audio/softsynth/mt32/mt32_file.cpp | 70 + audio/softsynth/mt32/mt32_file.h | 52 + audio/softsynth/mt32/mt32emu.h | 70 + audio/softsynth/mt32/part.cpp | 633 ++++++ audio/softsynth/mt32/part.h | 113 ++ audio/softsynth/mt32/partial.cpp | 968 +++++++++ audio/softsynth/mt32/partial.h | 148 ++ audio/softsynth/mt32/partialManager.cpp | 272 +++ audio/softsynth/mt32/partialManager.h | 56 + audio/softsynth/mt32/structures.h | 284 +++ audio/softsynth/mt32/synth.cpp | 1198 +++++++++++ audio/softsynth/mt32/synth.h | 300 +++ audio/softsynth/mt32/tables.cpp | 757 +++++++ audio/softsynth/mt32/tables.h | 116 ++ audio/softsynth/opl/dbopl.cpp | 1536 ++++++++++++++ audio/softsynth/opl/dbopl.h | 283 +++ audio/softsynth/opl/dosbox.cpp | 335 ++++ audio/softsynth/opl/dosbox.h | 110 + audio/softsynth/opl/mame.cpp | 1234 ++++++++++++ audio/softsynth/opl/mame.h | 202 ++ audio/softsynth/pcspk.cpp | 187 ++ audio/softsynth/pcspk.h | 88 + audio/softsynth/sid.cpp | 1456 ++++++++++++++ audio/softsynth/sid.h | 348 ++++ audio/softsynth/wave6581.cpp | 2098 ++++++++++++++++++++ audio/softsynth/ym2612.cpp | 789 ++++++++ audio/softsynth/ym2612.h | 179 ++ audio/timestamp.cpp | 212 ++ audio/timestamp.h | 251 +++ backends/audiocd/default/default-audiocd.cpp | 2 +- backends/audiocd/default/default-audiocd.h | 2 +- backends/midi/alsa.cpp | 4 +- backends/midi/camd.cpp | 4 +- backends/midi/coreaudio.cpp | 4 +- backends/midi/coremidi.cpp | 4 +- backends/midi/dmedia.cpp | 4 +- backends/midi/seq.cpp | 4 +- backends/midi/stmidi.cpp | 4 +- backends/midi/timidity.cpp | 4 +- backends/midi/windows.cpp | 4 +- backends/mixer/sdl/sdl-mixer.h | 2 +- backends/platform/android/android.cpp | 2 +- backends/platform/dc/audio.cpp | 2 +- backends/platform/dc/dc.h | 2 +- backends/platform/dc/dcmain.cpp | 2 +- backends/platform/ds/arm9/source/osystem_ds.h | 2 +- backends/platform/gph/gph-main.cpp | 2 +- backends/platform/iphone/osys_main.cpp | 4 +- backends/platform/iphone/osys_main.h | 2 +- backends/platform/n64/osys_n64.h | 2 +- backends/platform/null/null.cpp | 2 +- backends/platform/openpandora/op-main.cpp | 2 +- backends/platform/ps2/systemps2.cpp | 2 +- backends/platform/psp/mp3.cpp | 2 +- backends/platform/psp/osys_psp.cpp | 2 +- backends/platform/psp/osys_psp.h | 2 +- backends/platform/wii/osystem.h | 2 +- backends/platform/wince/wince-sdl.cpp | 4 +- backends/plugins/elf/memory-manager.cpp | 2 +- base/main.cpp | 4 +- base/plugins.cpp | 2 +- engines/agi/agi.cpp | 4 +- engines/agi/preagi.cpp | 2 +- engines/agi/preagi.h | 2 +- engines/agi/sound.h | 2 +- engines/agi/sound_2gs.h | 2 +- engines/agi/sound_coco3.h | 2 +- engines/agi/sound_midi.cpp | 6 +- engines/agi/sound_midi.h | 4 +- engines/agi/sound_pcjr.h | 2 +- engines/agi/sound_sarien.cpp | 2 +- engines/agi/sound_sarien.h | 2 +- engines/agos/agos.cpp | 4 +- engines/agos/animation.cpp | 4 +- engines/agos/animation.h | 2 +- engines/agos/midi.h | 4 +- engines/agos/midiparser_s1d.cpp | 4 +- engines/agos/res_snd.cpp | 6 +- engines/agos/sound.cpp | 16 +- engines/agos/sound.h | 4 +- engines/cine/sound.cpp | 8 +- engines/cine/sound.h | 2 +- engines/cruise/sound.cpp | 6 +- engines/cruise/sound.h | 6 +- engines/draci/music.cpp | 6 +- engines/draci/music.h | 4 +- engines/draci/sound.cpp | 12 +- engines/draci/sound.h | 2 +- engines/drascula/drascula.cpp | 2 +- engines/drascula/drascula.h | 2 +- engines/drascula/sound.cpp | 8 +- engines/engine.cpp | 2 +- engines/gob/gob.cpp | 2 +- engines/gob/inter_v2.cpp | 4 +- engines/gob/sound/adlib.h | 6 +- engines/gob/sound/bgatmosphere.h | 2 +- engines/gob/sound/infogrames.h | 4 +- engines/gob/sound/pcspeaker.h | 4 +- engines/gob/sound/protracker.cpp | 2 +- engines/gob/sound/protracker.h | 4 +- engines/gob/sound/soundblaster.h | 2 +- engines/gob/sound/sounddesc.cpp | 6 +- engines/gob/sound/soundmixer.cpp | 2 +- engines/gob/sound/soundmixer.h | 4 +- engines/groovie/groovie.cpp | 2 +- engines/groovie/music.cpp | 2 +- engines/groovie/music.h | 2 +- engines/groovie/player.h | 2 +- engines/groovie/roq.cpp | 4 +- engines/groovie/vdx.cpp | 4 +- engines/hugo/sound.cpp | 4 +- engines/hugo/sound.h | 8 +- engines/kyra/kyra_v1.cpp | 4 +- engines/kyra/kyra_v1.h | 2 +- engines/kyra/lol.cpp | 4 +- engines/kyra/sound.cpp | 16 +- engines/kyra/sound.h | 2 +- engines/kyra/sound_adlib.cpp | 6 +- engines/kyra/sound_amiga.cpp | 6 +- engines/kyra/sound_digital.cpp | 8 +- engines/kyra/sound_intern.h | 8 +- engines/kyra/sound_lol.cpp | 2 +- engines/kyra/sound_pcspk.cpp | 4 +- engines/kyra/sound_towns.cpp | 4 +- engines/kyra/vqa.cpp | 6 +- engines/kyra/vqa.h | 2 +- engines/lastexpress/data/snd.cpp | 4 +- engines/lastexpress/data/snd.h | 2 +- engines/lure/lure.h | 2 +- engines/lure/sound.cpp | 2 +- engines/lure/sound.h | 4 +- engines/m4/m4.cpp | 2 +- engines/m4/m4.h | 2 +- engines/m4/midi.h | 4 +- engines/m4/sound.cpp | 6 +- engines/m4/sound.h | 8 +- engines/made/made.cpp | 2 +- engines/made/made.h | 6 +- engines/made/music.cpp | 6 +- engines/made/music.h | 4 +- engines/made/pmvplayer.cpp | 2 +- engines/made/pmvplayer.h | 4 +- engines/made/resource.cpp | 4 +- engines/made/resource.h | 2 +- engines/made/scriptfuncs.cpp | 2 +- engines/mohawk/cstime.h | 2 +- engines/mohawk/livingbooks.h | 2 +- engines/mohawk/mohawk.cpp | 2 +- engines/mohawk/sound.cpp | 10 +- engines/mohawk/sound.h | 10 +- engines/parallaction/sound.h | 8 +- engines/parallaction/sound_br.cpp | 12 +- engines/parallaction/sound_ns.cpp | 8 +- engines/queen/midiadlib.cpp | 4 +- engines/queen/music.cpp | 2 +- engines/queen/music.h | 2 +- engines/queen/sound.cpp | 12 +- engines/queen/sound.h | 4 +- engines/saga/introproc_saga2.cpp | 2 +- engines/saga/music.cpp | 8 +- engines/saga/music.h | 12 +- engines/saga/saga.cpp | 2 +- engines/saga/saga.h | 2 +- engines/saga/shorten.cpp | 4 +- engines/saga/shorten.h | 2 +- engines/saga/sndres.cpp | 12 +- engines/saga/sound.cpp | 8 +- engines/saga/sound.h | 8 +- engines/sci/engine/ksound.cpp | 2 +- engines/sci/sound/audio.cpp | 16 +- engines/sci/sound/audio.h | 2 +- engines/sci/sound/drivers/adlib.cpp | 4 +- engines/sci/sound/drivers/amigamac.cpp | 2 +- engines/sci/sound/drivers/cms.cpp | 6 +- engines/sci/sound/drivers/midi.cpp | 4 +- engines/sci/sound/drivers/mididriver.h | 2 +- engines/sci/sound/drivers/pcjr.cpp | 2 +- engines/sci/sound/midiparser_sci.h | 2 +- engines/sci/sound/music.cpp | 4 +- engines/sci/sound/music.h | 4 +- engines/sci/sound/soundcmd.h | 2 +- engines/sci/video/robot_decoder.cpp | 2 +- engines/sci/video/robot_decoder.h | 4 +- engines/scumm/dialogs.cpp | 4 +- engines/scumm/he/animation_he.cpp | 2 +- engines/scumm/he/animation_he.h | 2 +- engines/scumm/he/cup_player_he.cpp | 6 +- engines/scumm/he/resource_he.cpp | 2 +- engines/scumm/he/sound_he.cpp | 18 +- engines/scumm/imuse/imuse_internal.h | 2 +- engines/scumm/imuse/imuse_player.cpp | 2 +- engines/scumm/imuse/instrument.cpp | 2 +- engines/scumm/imuse_digi/dimuse.cpp | 6 +- engines/scumm/imuse_digi/dimuse.h | 4 +- engines/scumm/imuse_digi/dimuse_script.cpp | 4 +- engines/scumm/imuse_digi/dimuse_sndmgr.cpp | 8 +- engines/scumm/imuse_digi/dimuse_sndmgr.h | 2 +- engines/scumm/imuse_digi/dimuse_track.cpp | 4 +- engines/scumm/midiparser_ro.cpp | 4 +- engines/scumm/player_mod.cpp | 6 +- engines/scumm/player_mod.h | 4 +- engines/scumm/player_nes.cpp | 2 +- engines/scumm/player_nes.h | 4 +- engines/scumm/player_pce.h | 4 +- engines/scumm/player_sid.cpp | 2 +- engines/scumm/player_sid.h | 6 +- engines/scumm/player_towns.h | 2 +- engines/scumm/player_v2base.h | 4 +- engines/scumm/player_v2cms.cpp | 6 +- engines/scumm/player_v4a.h | 4 +- engines/scumm/saveload.cpp | 2 +- engines/scumm/script_v8.cpp | 2 +- engines/scumm/scumm.cpp | 2 +- engines/scumm/scumm.h | 2 +- engines/scumm/smush/smush_mixer.cpp | 4 +- engines/scumm/smush/smush_player.cpp | 8 +- engines/scumm/sound.cpp | 18 +- engines/scumm/sound.h | 4 +- engines/sky/intro.cpp | 4 +- engines/sky/intro.h | 2 +- engines/sky/music/adlibchannel.h | 2 +- engines/sky/music/adlibmusic.cpp | 2 +- engines/sky/music/adlibmusic.h | 6 +- engines/sky/music/gmchannel.cpp | 2 +- engines/sky/music/gmmusic.cpp | 2 +- engines/sky/music/mt32music.cpp | 2 +- engines/sky/sky.cpp | 4 +- engines/sky/sound.cpp | 4 +- engines/sky/sound.h | 2 +- engines/sword1/animation.cpp | 2 +- engines/sword1/animation.h | 2 +- engines/sword1/logic.h | 2 +- engines/sword1/music.cpp | 16 +- engines/sword1/music.h | 6 +- engines/sword1/sound.cpp | 14 +- engines/sword1/sound.h | 2 +- engines/sword2/animation.h | 2 +- engines/sword2/music.cpp | 12 +- engines/sword2/sound.cpp | 4 +- engines/sword2/sound.h | 4 +- engines/sword25/fmv/theora_decoder.cpp | 2 +- engines/sword25/fmv/theora_decoder.h | 4 +- engines/sword25/sfx/soundengine.cpp | 2 +- engines/sword25/sfx/soundengine.h | 4 +- engines/teenagent/music.h | 2 +- engines/teenagent/teenagent.cpp | 4 +- engines/teenagent/teenagent.h | 4 +- engines/testbed/midi.cpp | 2 +- engines/testbed/midi.h | 2 +- engines/testbed/sound.cpp | 2 +- engines/testbed/sound.h | 2 +- engines/tinsel/bmv.cpp | 2 +- engines/tinsel/bmv.h | 4 +- engines/tinsel/config.cpp | 2 +- engines/tinsel/music.cpp | 8 +- engines/tinsel/music.h | 8 +- engines/tinsel/sound.cpp | 14 +- engines/tinsel/sound.h | 2 +- engines/tinsel/tinsel.h | 4 +- engines/toon/audio.h | 4 +- engines/touche/midi.cpp | 2 +- engines/touche/midi.h | 2 +- engines/touche/resource.cpp | 14 +- engines/touche/touche.cpp | 2 +- engines/touche/touche.h | 2 +- engines/tucker/resource.cpp | 10 +- engines/tucker/sequences.cpp | 6 +- engines/tucker/tucker.h | 2 +- gui/launcher.cpp | 2 +- gui/options.cpp | 8 +- gui/options.h | 2 +- po/POTFILES | 22 +- po/ca_ES.po | 26 +- po/cs_CZ.po | 26 +- po/da_DA.po | 26 +- po/de_DE.po | 26 +- po/es_ES.po | 26 +- po/fr_FR.po | 26 +- po/hu_HU.po | 26 +- po/it_IT.po | 26 +- po/nb_NO.po | 26 +- po/nn_NO.po | 26 +- po/pl_PL.po | 26 +- po/pt_BR.po | 26 +- po/ru_RU.po | 26 +- po/scummvm.pot | 26 +- po/uk_UA.po | 26 +- sound/audiostream.cpp | 407 ---- sound/audiostream.h | 371 ---- sound/decoders/adpcm.cpp | 851 -------- sound/decoders/adpcm.h | 90 - sound/decoders/aiff.cpp | 186 -- sound/decoders/aiff.h | 71 - sound/decoders/flac.cpp | 745 ------- sound/decoders/flac.h | 75 - sound/decoders/iff_sound.cpp | 130 -- sound/decoders/iff_sound.h | 47 - sound/decoders/mac_snd.cpp | 116 -- sound/decoders/mac_snd.h | 58 - sound/decoders/mp3.cpp | 375 ---- sound/decoders/mp3.h | 76 - sound/decoders/raw.cpp | 356 ---- sound/decoders/raw.h | 153 -- sound/decoders/vag.cpp | 150 -- sound/decoders/vag.h | 60 - sound/decoders/voc.cpp | 403 ---- sound/decoders/voc.h | 107 - sound/decoders/vorbis.cpp | 262 --- sound/decoders/vorbis.h | 75 - sound/decoders/wave.cpp | 194 -- sound/decoders/wave.h | 84 - sound/fmopl.cpp | 197 -- sound/fmopl.h | 179 -- sound/mididrv.cpp | 326 --- sound/mididrv.h | 288 --- sound/midiparser.cpp | 467 ----- sound/midiparser.h | 404 ---- sound/midiparser_smf.cpp | 384 ---- sound/midiparser_xmidi.cpp | 375 ---- sound/mixer.cpp | 556 ------ sound/mixer.h | 265 --- sound/mixer_intern.h | 135 -- sound/mods/infogrames.cpp | 470 ----- sound/mods/infogrames.h | 148 -- sound/mods/maxtrax.cpp | 1040 ---------- sound/mods/maxtrax.h | 225 --- sound/mods/module.cpp | 252 --- sound/mods/module.h | 90 - sound/mods/paula.cpp | 212 -- sound/mods/paula.h | 210 -- sound/mods/protracker.cpp | 466 ----- sound/mods/protracker.h | 57 - sound/mods/rjp1.cpp | 582 ------ sound/mods/rjp1.h | 50 - sound/mods/soundfx.cpp | 275 --- sound/mods/soundfx.h | 53 - sound/mods/tfmx.cpp | 1193 ----------- sound/mods/tfmx.h | 284 --- sound/module.mk | 61 - sound/mpu401.cpp | 145 -- sound/mpu401.h | 92 - sound/musicplugin.cpp | 64 - sound/musicplugin.h | 125 -- sound/null.cpp | 62 - sound/null.h | 56 - sound/rate.cpp | 358 ---- sound/rate.h | 90 - sound/rate_arm.cpp | 465 ----- sound/rate_arm_asm.s | 687 ------- sound/softsynth/adlib.cpp | 1617 --------------- sound/softsynth/appleiigs.cpp | 57 - sound/softsynth/cms.cpp | 376 ---- sound/softsynth/cms.h | 92 - sound/softsynth/emumidi.h | 116 -- sound/softsynth/fluidsynth.cpp | 254 --- sound/softsynth/fmtowns_pc98/towns_audio.cpp | 1583 --------------- sound/softsynth/fmtowns_pc98/towns_audio.h | 179 -- sound/softsynth/fmtowns_pc98/towns_euphony.cpp | 905 --------- sound/softsynth/fmtowns_pc98/towns_euphony.h | 187 -- sound/softsynth/fmtowns_pc98/towns_pc98_driver.cpp | 1428 ------------- sound/softsynth/fmtowns_pc98/towns_pc98_driver.h | 135 -- .../softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp | 1548 --------------- sound/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h | 196 -- sound/softsynth/mt32.cpp | 573 ------ sound/softsynth/mt32/freeverb.cpp | 310 --- sound/softsynth/mt32/freeverb.h | 244 --- sound/softsynth/mt32/i386.cpp | 849 -------- sound/softsynth/mt32/i386.h | 49 - sound/softsynth/mt32/module.mk | 14 - sound/softsynth/mt32/mt32_file.cpp | 70 - sound/softsynth/mt32/mt32_file.h | 52 - sound/softsynth/mt32/mt32emu.h | 70 - sound/softsynth/mt32/part.cpp | 633 ------ sound/softsynth/mt32/part.h | 113 -- sound/softsynth/mt32/partial.cpp | 968 --------- sound/softsynth/mt32/partial.h | 148 -- sound/softsynth/mt32/partialManager.cpp | 272 --- sound/softsynth/mt32/partialManager.h | 56 - sound/softsynth/mt32/structures.h | 284 --- sound/softsynth/mt32/synth.cpp | 1198 ----------- sound/softsynth/mt32/synth.h | 300 --- sound/softsynth/mt32/tables.cpp | 757 ------- sound/softsynth/mt32/tables.h | 116 -- sound/softsynth/opl/dbopl.cpp | 1536 -------------- sound/softsynth/opl/dbopl.h | 283 --- sound/softsynth/opl/dosbox.cpp | 335 ---- sound/softsynth/opl/dosbox.h | 110 - sound/softsynth/opl/mame.cpp | 1234 ------------ sound/softsynth/opl/mame.h | 202 -- sound/softsynth/pcspk.cpp | 187 -- sound/softsynth/pcspk.h | 88 - sound/softsynth/sid.cpp | 1456 -------------- sound/softsynth/sid.h | 348 ---- sound/softsynth/wave6581.cpp | 2098 -------------------- sound/softsynth/ym2612.cpp | 789 -------- sound/softsynth/ym2612.h | 179 -- sound/timestamp.cpp | 212 -- sound/timestamp.h | 251 --- test/audio/audiostream.h | 209 ++ test/audio/helper.h | 112 ++ test/audio/raw.h | 358 ++++ test/audio/timestamp.h | 241 +++ test/module.mk | 4 +- test/sound/audiostream.h | 209 -- test/sound/helper.h | 112 -- test/sound/raw.h | 358 ---- test/sound/timestamp.h | 241 --- video/avi_decoder.cpp | 8 +- video/avi_decoder.h | 4 +- video/codecs/qdm2.cpp | 2 +- video/coktel_decoder.cpp | 4 +- video/coktel_decoder.h | 2 +- video/qt_decoder.cpp | 4 +- video/qt_decoder.h | 4 +- video/smk_decoder.cpp | 6 +- video/smk_decoder.h | 2 +- video/video_decoder.h | 2 +- 500 files changed, 43544 insertions(+), 43544 deletions(-) create mode 100644 audio/audiostream.cpp create mode 100644 audio/audiostream.h create mode 100644 audio/decoders/adpcm.cpp create mode 100644 audio/decoders/adpcm.h create mode 100644 audio/decoders/aiff.cpp create mode 100644 audio/decoders/aiff.h create mode 100644 audio/decoders/flac.cpp create mode 100644 audio/decoders/flac.h create mode 100644 audio/decoders/iff_sound.cpp create mode 100644 audio/decoders/iff_sound.h create mode 100644 audio/decoders/mac_snd.cpp create mode 100644 audio/decoders/mac_snd.h create mode 100644 audio/decoders/mp3.cpp create mode 100644 audio/decoders/mp3.h create mode 100644 audio/decoders/raw.cpp create mode 100644 audio/decoders/raw.h create mode 100644 audio/decoders/vag.cpp create mode 100644 audio/decoders/vag.h create mode 100644 audio/decoders/voc.cpp create mode 100644 audio/decoders/voc.h create mode 100644 audio/decoders/vorbis.cpp create mode 100644 audio/decoders/vorbis.h create mode 100644 audio/decoders/wave.cpp create mode 100644 audio/decoders/wave.h create mode 100644 audio/fmopl.cpp create mode 100644 audio/fmopl.h create mode 100644 audio/mididrv.cpp create mode 100644 audio/mididrv.h create mode 100644 audio/midiparser.cpp create mode 100644 audio/midiparser.h create mode 100644 audio/midiparser_smf.cpp create mode 100644 audio/midiparser_xmidi.cpp create mode 100644 audio/mixer.cpp create mode 100644 audio/mixer.h create mode 100644 audio/mixer_intern.h create mode 100644 audio/mods/infogrames.cpp create mode 100644 audio/mods/infogrames.h create mode 100644 audio/mods/maxtrax.cpp create mode 100644 audio/mods/maxtrax.h create mode 100644 audio/mods/module.cpp create mode 100644 audio/mods/module.h create mode 100644 audio/mods/paula.cpp create mode 100644 audio/mods/paula.h create mode 100644 audio/mods/protracker.cpp create mode 100644 audio/mods/protracker.h create mode 100644 audio/mods/rjp1.cpp create mode 100644 audio/mods/rjp1.h create mode 100644 audio/mods/soundfx.cpp create mode 100644 audio/mods/soundfx.h create mode 100644 audio/mods/tfmx.cpp create mode 100644 audio/mods/tfmx.h create mode 100644 audio/module.mk create mode 100644 audio/mpu401.cpp create mode 100644 audio/mpu401.h create mode 100644 audio/musicplugin.cpp create mode 100644 audio/musicplugin.h create mode 100644 audio/null.cpp create mode 100644 audio/null.h create mode 100644 audio/rate.cpp create mode 100644 audio/rate.h create mode 100644 audio/rate_arm.cpp create mode 100644 audio/rate_arm_asm.s create mode 100644 audio/softsynth/adlib.cpp create mode 100644 audio/softsynth/appleiigs.cpp create mode 100644 audio/softsynth/cms.cpp create mode 100644 audio/softsynth/cms.h create mode 100644 audio/softsynth/emumidi.h create mode 100644 audio/softsynth/fluidsynth.cpp create mode 100644 audio/softsynth/fmtowns_pc98/towns_audio.cpp create mode 100644 audio/softsynth/fmtowns_pc98/towns_audio.h create mode 100644 audio/softsynth/fmtowns_pc98/towns_euphony.cpp create mode 100644 audio/softsynth/fmtowns_pc98/towns_euphony.h create mode 100644 audio/softsynth/fmtowns_pc98/towns_pc98_driver.cpp create mode 100644 audio/softsynth/fmtowns_pc98/towns_pc98_driver.h create mode 100644 audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp create mode 100644 audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h create mode 100644 audio/softsynth/mt32.cpp create mode 100644 audio/softsynth/mt32/freeverb.cpp create mode 100644 audio/softsynth/mt32/freeverb.h create mode 100644 audio/softsynth/mt32/i386.cpp create mode 100644 audio/softsynth/mt32/i386.h create mode 100644 audio/softsynth/mt32/module.mk create mode 100644 audio/softsynth/mt32/mt32_file.cpp create mode 100644 audio/softsynth/mt32/mt32_file.h create mode 100644 audio/softsynth/mt32/mt32emu.h create mode 100644 audio/softsynth/mt32/part.cpp create mode 100644 audio/softsynth/mt32/part.h create mode 100644 audio/softsynth/mt32/partial.cpp create mode 100644 audio/softsynth/mt32/partial.h create mode 100644 audio/softsynth/mt32/partialManager.cpp create mode 100644 audio/softsynth/mt32/partialManager.h create mode 100644 audio/softsynth/mt32/structures.h create mode 100644 audio/softsynth/mt32/synth.cpp create mode 100644 audio/softsynth/mt32/synth.h create mode 100644 audio/softsynth/mt32/tables.cpp create mode 100644 audio/softsynth/mt32/tables.h create mode 100644 audio/softsynth/opl/dbopl.cpp create mode 100644 audio/softsynth/opl/dbopl.h create mode 100644 audio/softsynth/opl/dosbox.cpp create mode 100644 audio/softsynth/opl/dosbox.h create mode 100644 audio/softsynth/opl/mame.cpp create mode 100644 audio/softsynth/opl/mame.h create mode 100644 audio/softsynth/pcspk.cpp create mode 100644 audio/softsynth/pcspk.h create mode 100644 audio/softsynth/sid.cpp create mode 100644 audio/softsynth/sid.h create mode 100644 audio/softsynth/wave6581.cpp create mode 100644 audio/softsynth/ym2612.cpp create mode 100644 audio/softsynth/ym2612.h create mode 100644 audio/timestamp.cpp create mode 100644 audio/timestamp.h delete mode 100644 sound/audiostream.cpp delete mode 100644 sound/audiostream.h delete mode 100644 sound/decoders/adpcm.cpp delete mode 100644 sound/decoders/adpcm.h delete mode 100644 sound/decoders/aiff.cpp delete mode 100644 sound/decoders/aiff.h delete mode 100644 sound/decoders/flac.cpp delete mode 100644 sound/decoders/flac.h delete mode 100644 sound/decoders/iff_sound.cpp delete mode 100644 sound/decoders/iff_sound.h delete mode 100644 sound/decoders/mac_snd.cpp delete mode 100644 sound/decoders/mac_snd.h delete mode 100644 sound/decoders/mp3.cpp delete mode 100644 sound/decoders/mp3.h delete mode 100644 sound/decoders/raw.cpp delete mode 100644 sound/decoders/raw.h delete mode 100644 sound/decoders/vag.cpp delete mode 100644 sound/decoders/vag.h delete mode 100644 sound/decoders/voc.cpp delete mode 100644 sound/decoders/voc.h delete mode 100644 sound/decoders/vorbis.cpp delete mode 100644 sound/decoders/vorbis.h delete mode 100644 sound/decoders/wave.cpp delete mode 100644 sound/decoders/wave.h delete mode 100644 sound/fmopl.cpp delete mode 100644 sound/fmopl.h delete mode 100644 sound/mididrv.cpp delete mode 100644 sound/mididrv.h delete mode 100644 sound/midiparser.cpp delete mode 100644 sound/midiparser.h delete mode 100644 sound/midiparser_smf.cpp delete mode 100644 sound/midiparser_xmidi.cpp delete mode 100644 sound/mixer.cpp delete mode 100644 sound/mixer.h delete mode 100644 sound/mixer_intern.h delete mode 100644 sound/mods/infogrames.cpp delete mode 100644 sound/mods/infogrames.h delete mode 100644 sound/mods/maxtrax.cpp delete mode 100644 sound/mods/maxtrax.h delete mode 100644 sound/mods/module.cpp delete mode 100644 sound/mods/module.h delete mode 100644 sound/mods/paula.cpp delete mode 100644 sound/mods/paula.h delete mode 100644 sound/mods/protracker.cpp delete mode 100644 sound/mods/protracker.h delete mode 100644 sound/mods/rjp1.cpp delete mode 100644 sound/mods/rjp1.h delete mode 100644 sound/mods/soundfx.cpp delete mode 100644 sound/mods/soundfx.h delete mode 100644 sound/mods/tfmx.cpp delete mode 100644 sound/mods/tfmx.h delete mode 100644 sound/module.mk delete mode 100644 sound/mpu401.cpp delete mode 100644 sound/mpu401.h delete mode 100644 sound/musicplugin.cpp delete mode 100644 sound/musicplugin.h delete mode 100644 sound/null.cpp delete mode 100644 sound/null.h delete mode 100644 sound/rate.cpp delete mode 100644 sound/rate.h delete mode 100644 sound/rate_arm.cpp delete mode 100644 sound/rate_arm_asm.s delete mode 100644 sound/softsynth/adlib.cpp delete mode 100644 sound/softsynth/appleiigs.cpp delete mode 100644 sound/softsynth/cms.cpp delete mode 100644 sound/softsynth/cms.h delete mode 100644 sound/softsynth/emumidi.h delete mode 100644 sound/softsynth/fluidsynth.cpp delete mode 100644 sound/softsynth/fmtowns_pc98/towns_audio.cpp delete mode 100644 sound/softsynth/fmtowns_pc98/towns_audio.h delete mode 100644 sound/softsynth/fmtowns_pc98/towns_euphony.cpp delete mode 100644 sound/softsynth/fmtowns_pc98/towns_euphony.h delete mode 100644 sound/softsynth/fmtowns_pc98/towns_pc98_driver.cpp delete mode 100644 sound/softsynth/fmtowns_pc98/towns_pc98_driver.h delete mode 100644 sound/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp delete mode 100644 sound/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h delete mode 100644 sound/softsynth/mt32.cpp delete mode 100644 sound/softsynth/mt32/freeverb.cpp delete mode 100644 sound/softsynth/mt32/freeverb.h delete mode 100644 sound/softsynth/mt32/i386.cpp delete mode 100644 sound/softsynth/mt32/i386.h delete mode 100644 sound/softsynth/mt32/module.mk delete mode 100644 sound/softsynth/mt32/mt32_file.cpp delete mode 100644 sound/softsynth/mt32/mt32_file.h delete mode 100644 sound/softsynth/mt32/mt32emu.h delete mode 100644 sound/softsynth/mt32/part.cpp delete mode 100644 sound/softsynth/mt32/part.h delete mode 100644 sound/softsynth/mt32/partial.cpp delete mode 100644 sound/softsynth/mt32/partial.h delete mode 100644 sound/softsynth/mt32/partialManager.cpp delete mode 100644 sound/softsynth/mt32/partialManager.h delete mode 100644 sound/softsynth/mt32/structures.h delete mode 100644 sound/softsynth/mt32/synth.cpp delete mode 100644 sound/softsynth/mt32/synth.h delete mode 100644 sound/softsynth/mt32/tables.cpp delete mode 100644 sound/softsynth/mt32/tables.h delete mode 100644 sound/softsynth/opl/dbopl.cpp delete mode 100644 sound/softsynth/opl/dbopl.h delete mode 100644 sound/softsynth/opl/dosbox.cpp delete mode 100644 sound/softsynth/opl/dosbox.h delete mode 100644 sound/softsynth/opl/mame.cpp delete mode 100644 sound/softsynth/opl/mame.h delete mode 100644 sound/softsynth/pcspk.cpp delete mode 100644 sound/softsynth/pcspk.h delete mode 100644 sound/softsynth/sid.cpp delete mode 100644 sound/softsynth/sid.h delete mode 100644 sound/softsynth/wave6581.cpp delete mode 100644 sound/softsynth/ym2612.cpp delete mode 100644 sound/softsynth/ym2612.h delete mode 100644 sound/timestamp.cpp delete mode 100644 sound/timestamp.h create mode 100644 test/audio/audiostream.h create mode 100644 test/audio/helper.h create mode 100644 test/audio/raw.h create mode 100644 test/audio/timestamp.h delete mode 100644 test/sound/audiostream.h delete mode 100644 test/sound/helper.h delete mode 100644 test/sound/raw.h delete mode 100644 test/sound/timestamp.h diff --git a/Makefile.common b/Makefile.common index f8a665ce24..eb3165382f 100644 --- a/Makefile.common +++ b/Makefile.common @@ -27,12 +27,12 @@ MODULES += \ engines \ video \ graphics \ - sound \ + audio \ common \ po ifdef USE_MT32EMU -MODULES += sound/softsynth/mt32 +MODULES += audio/softsynth/mt32 endif ###################################################################### diff --git a/audio/audiostream.cpp b/audio/audiostream.cpp new file mode 100644 index 0000000000..46b2846137 --- /dev/null +++ b/audio/audiostream.cpp @@ -0,0 +1,407 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/debug.h" +#include "common/endian.h" +#include "common/file.h" +#include "common/queue.h" +#include "common/util.h" + +#include "audio/audiostream.h" +#include "audio/decoders/flac.h" +#include "audio/mixer.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/vorbis.h" + + +namespace Audio { + +struct StreamFileFormat { + /** Decodername */ + const char *decoderName; + const char *fileExtension; + /** + * Pointer to a function which tries to open a file of type StreamFormat. + * Return NULL in case of an error (invalid/nonexisting file). + */ + SeekableAudioStream *(*openStreamFile)(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse); +}; + +static const StreamFileFormat STREAM_FILEFORMATS[] = { + /* decoderName, fileExt, openStreamFuntion */ +#ifdef USE_FLAC + { "FLAC", ".flac", makeFLACStream }, + { "FLAC", ".fla", makeFLACStream }, +#endif +#ifdef USE_VORBIS + { "Ogg Vorbis", ".ogg", makeVorbisStream }, +#endif +#ifdef USE_MAD + { "MPEG Layer 3", ".mp3", makeMP3Stream }, +#endif + + { NULL, NULL, NULL } // Terminator +}; + +SeekableAudioStream *SeekableAudioStream::openStreamFile(const Common::String &basename) { + SeekableAudioStream *stream = NULL; + Common::File *fileHandle = new Common::File(); + + for (int i = 0; i < ARRAYSIZE(STREAM_FILEFORMATS)-1 && stream == NULL; ++i) { + Common::String filename = basename + STREAM_FILEFORMATS[i].fileExtension; + fileHandle->open(filename); + if (fileHandle->isOpen()) { + // Create the stream object + stream = STREAM_FILEFORMATS[i].openStreamFile(fileHandle, DisposeAfterUse::YES); + fileHandle = 0; + break; + } + } + + delete fileHandle; + + if (stream == NULL) + debug(1, "SeekableAudioStream::openStreamFile: Could not open compressed AudioFile %s", basename.c_str()); + + return stream; +} + +#pragma mark - +#pragma mark --- LoopingAudioStream --- +#pragma mark - + +LoopingAudioStream::LoopingAudioStream(RewindableAudioStream *stream, uint loops, DisposeAfterUse::Flag disposeAfterUse) + : _parent(stream), _disposeAfterUse(disposeAfterUse), _loops(loops), _completeIterations(0) { + assert(stream); + + if (!stream->rewind()) { + // TODO: Properly indicate error + _loops = _completeIterations = 1; + } +} + +LoopingAudioStream::~LoopingAudioStream() { + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _parent; +} + +int LoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) { + if ((_loops && _completeIterations == _loops) || !numSamples) + return 0; + + int samplesRead = _parent->readBuffer(buffer, numSamples); + + if (_parent->endOfStream()) { + ++_completeIterations; + if (_completeIterations == _loops) + return samplesRead; + + const int remainingSamples = numSamples - samplesRead; + + if (!_parent->rewind()) { + // TODO: Properly indicate error + _loops = _completeIterations = 1; + return samplesRead; + } + + return samplesRead + readBuffer(buffer + samplesRead, remainingSamples); + } + + return samplesRead; +} + +bool LoopingAudioStream::endOfData() const { + return (_loops != 0 && (_completeIterations == _loops)); +} + +AudioStream *makeLoopingAudioStream(RewindableAudioStream *stream, uint loops) { + if (loops != 1) + return new LoopingAudioStream(stream, loops); + else + return stream; +} + +AudioStream *makeLoopingAudioStream(SeekableAudioStream *stream, Timestamp start, Timestamp end, uint loops) { + if (!start.totalNumberOfFrames() && (!end.totalNumberOfFrames() || end == stream->getLength())) { + return makeLoopingAudioStream(stream, loops); + } else { + if (!end.totalNumberOfFrames()) + end = stream->getLength(); + + if (start >= end) { + warning("makeLoopingAudioStream: start (%d) >= end (%d)", start.msecs(), end.msecs()); + delete stream; + return 0; + } + + return makeLoopingAudioStream(new SubSeekableAudioStream(stream, start, end), loops); + } +} + +#pragma mark - +#pragma mark --- SubLoopingAudioStream --- +#pragma mark - + +SubLoopingAudioStream::SubLoopingAudioStream(SeekableAudioStream *stream, + uint loops, + const Timestamp loopStart, + const Timestamp loopEnd, + DisposeAfterUse::Flag disposeAfterUse) + : _parent(stream), _disposeAfterUse(disposeAfterUse), _loops(loops), + _pos(0, getRate() * (isStereo() ? 2 : 1)), + _loopStart(convertTimeToStreamPos(loopStart, getRate(), isStereo())), + _loopEnd(convertTimeToStreamPos(loopEnd, getRate(), isStereo())), + _done(false) { + assert(loopStart < loopEnd); + + if (!_parent->rewind()) + _done = true; +} + +SubLoopingAudioStream::~SubLoopingAudioStream() { + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _parent; +} + +int SubLoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) { + if (_done) + return 0; + + int framesLeft = MIN(_loopEnd.frameDiff(_pos), numSamples); + int framesRead = _parent->readBuffer(buffer, framesLeft); + _pos = _pos.addFrames(framesRead); + + if (framesRead < framesLeft && _parent->endOfData()) { + // TODO: Proper error indication. + _done = true; + return framesRead; + } else if (_pos == _loopEnd) { + if (_loops != 0) { + --_loops; + if (!_loops) { + _done = true; + return framesRead; + } + } + + if (!_parent->seek(_loopStart)) { + // TODO: Proper error indication. + _done = true; + return framesRead; + } + + _pos = _loopStart; + framesLeft = numSamples - framesLeft; + return framesRead + readBuffer(buffer + framesRead, framesLeft); + } else { + return framesRead; + } +} + +#pragma mark - +#pragma mark --- SubSeekableAudioStream --- +#pragma mark - + +SubSeekableAudioStream::SubSeekableAudioStream(SeekableAudioStream *parent, const Timestamp start, const Timestamp end, DisposeAfterUse::Flag disposeAfterUse) + : _parent(parent), _disposeAfterUse(disposeAfterUse), + _start(convertTimeToStreamPos(start, getRate(), isStereo())), + _pos(0, getRate() * (isStereo() ? 2 : 1)), + _length(convertTimeToStreamPos(end - start, getRate(), isStereo())) { + + assert(_length.totalNumberOfFrames() % (isStereo() ? 2 : 1) == 0); + _parent->seek(_start); +} + +SubSeekableAudioStream::~SubSeekableAudioStream() { + if (_disposeAfterUse) + delete _parent; +} + +int SubSeekableAudioStream::readBuffer(int16 *buffer, const int numSamples) { + int framesLeft = MIN(_length.frameDiff(_pos), numSamples); + int framesRead = _parent->readBuffer(buffer, framesLeft); + _pos = _pos.addFrames(framesRead); + return framesRead; +} + +bool SubSeekableAudioStream::seek(const Timestamp &where) { + _pos = convertTimeToStreamPos(where, getRate(), isStereo()); + if (_pos > _length) { + _pos = _length; + return false; + } + + if (_parent->seek(_pos + _start)) { + return true; + } else { + _pos = _length; + return false; + } +} + +#pragma mark - +#pragma mark --- Queueing audio stream --- +#pragma mark - + + +void QueuingAudioStream::queueBuffer(byte *data, uint32 size, DisposeAfterUse::Flag disposeAfterUse, byte flags) { + AudioStream *stream = makeRawStream(data, size, getRate(), flags, disposeAfterUse); + queueAudioStream(stream, DisposeAfterUse::YES); +} + + +class QueuingAudioStreamImpl : public QueuingAudioStream { +private: + /** + * We queue a number of (pointers to) audio stream objects. + * In addition, we need to remember for each stream whether + * to dispose it after all data has been read from it. + * Hence, we don't store pointers to stream objects directly, + * but rather StreamHolder structs. + */ + struct StreamHolder { + AudioStream *_stream; + DisposeAfterUse::Flag _disposeAfterUse; + StreamHolder(AudioStream *stream, DisposeAfterUse::Flag disposeAfterUse) + : _stream(stream), + _disposeAfterUse(disposeAfterUse) {} + }; + + /** + * The sampling rate of this audio stream. + */ + const int _rate; + + /** + * Whether this audio stream is mono (=false) or stereo (=true). + */ + const int _stereo; + + /** + * This flag is set by the finish() method only. See there for more details. + */ + bool _finished; + + /** + * A mutex to avoid access problems (causing e.g. corruption of + * the linked list) in thread aware environments. + */ + Common::Mutex _mutex; + + /** + * The queue of audio streams. + */ + Common::Queue _queue; + +public: + QueuingAudioStreamImpl(int rate, bool stereo) + : _rate(rate), _stereo(stereo), _finished(false) {} + ~QueuingAudioStreamImpl(); + + // Implement the AudioStream API + virtual int readBuffer(int16 *buffer, const int numSamples); + virtual bool isStereo() const { return _stereo; } + virtual int getRate() const { return _rate; } + virtual bool endOfData() const { + //Common::StackLock lock(_mutex); + return _queue.empty(); + } + virtual bool endOfStream() const { return _finished && _queue.empty(); } + + // Implement the QueuingAudioStream API + virtual void queueAudioStream(AudioStream *stream, DisposeAfterUse::Flag disposeAfterUse); + virtual void finish() { _finished = true; } + + uint32 numQueuedStreams() const { + //Common::StackLock lock(_mutex); + return _queue.size(); + } +}; + +QueuingAudioStreamImpl::~QueuingAudioStreamImpl() { + while (!_queue.empty()) { + StreamHolder tmp = _queue.pop(); + if (tmp._disposeAfterUse == DisposeAfterUse::YES) + delete tmp._stream; + } +} + +void QueuingAudioStreamImpl::queueAudioStream(AudioStream *stream, DisposeAfterUse::Flag disposeAfterUse) { + assert(!_finished); + if ((stream->getRate() != getRate()) || (stream->isStereo() != isStereo())) + error("QueuingAudioStreamImpl::queueAudioStream: stream has mismatched parameters"); + + Common::StackLock lock(_mutex); + _queue.push(StreamHolder(stream, disposeAfterUse)); +} + +int QueuingAudioStreamImpl::readBuffer(int16 *buffer, const int numSamples) { + Common::StackLock lock(_mutex); + int samplesDecoded = 0; + + while (samplesDecoded < numSamples && !_queue.empty()) { + AudioStream *stream = _queue.front()._stream; + samplesDecoded += stream->readBuffer(buffer + samplesDecoded, numSamples - samplesDecoded); + + if (stream->endOfData()) { + StreamHolder tmp = _queue.pop(); + if (tmp._disposeAfterUse == DisposeAfterUse::YES) + delete stream; + } + } + + return samplesDecoded; +} + +QueuingAudioStream *makeQueuingAudioStream(int rate, bool stereo) { + return new QueuingAudioStreamImpl(rate, stereo); +} + +Timestamp convertTimeToStreamPos(const Timestamp &where, int rate, bool isStereo) { + Timestamp result(where.convertToFramerate(rate * (isStereo ? 2 : 1))); + + // When the Stream is a stereo stream, we have to assure + // that the sample position is an even number. + if (isStereo && (result.totalNumberOfFrames() & 1)) + result = result.addFrames(-1); // We cut off one sample here. + + // Since Timestamp allows sub-frame-precision it might lead to odd behaviors + // when we would just return result. + // + // An example is when converting the timestamp 500ms to a 11025 Hz based + // stream. It would have an internal frame counter of 5512.5. Now when + // doing calculations at frame precision, this might lead to unexpected + // results: The frame difference between a timestamp 1000ms and the above + // mentioned timestamp (both with 11025 as framerate) would be 5512, + // instead of 5513, which is what a frame-precision based code would expect. + // + // By creating a new Timestamp with the given parameters, we create a + // Timestamp with frame-precision, which just drops a sub-frame-precision + // information (i.e. rounds down). + return Timestamp(result.secs(), result.numberOfFrames(), result.framerate()); +} + +} // End of namespace Audio diff --git a/audio/audiostream.h b/audio/audiostream.h new file mode 100644 index 0000000000..cd6456cc70 --- /dev/null +++ b/audio/audiostream.h @@ -0,0 +1,371 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SOUND_AUDIOSTREAM_H +#define SOUND_AUDIOSTREAM_H + +#include "common/util.h" +#include "common/scummsys.h" +#include "common/types.h" + +#include "audio/timestamp.h" + +namespace Audio { + +class SeekableAudioStream; + +/** + * Generic audio input stream. Subclasses of this are used to feed arbitrary + * sampled audio data into ScummVM's audio mixer. + */ +class AudioStream { +public: + virtual ~AudioStream() {} + + /** + * Fill the given buffer with up to numSamples samples. Returns the actual + * number of samples read, or -1 if a critical error occurred (note: you + * *must* check if this value is less than what you requested, this can + * happen when the stream is fully used up). + * + * Data has to be in native endianess, 16 bit per sample, signed. For stereo + * stream, buffer will be filled with interleaved left and right channel + * samples, starting with a left sample. Furthermore, the samples in the + * left and right are summed up. So if you request 4 samples from a stereo + * stream, you will get a total of two left channel and two right channel + * samples. + */ + virtual int readBuffer(int16 *buffer, const int numSamples) = 0; + + /** Is this a stereo stream? */ + virtual bool isStereo() const = 0; + + /** Sample rate of the stream. */ + virtual int getRate() const = 0; + + /** + * End of data reached? If this returns true, it means that at this + * time there is no data available in the stream. However there may be + * more data in the future. + * This is used by e.g. a rate converter to decide whether to keep on + * converting data or stop. + */ + virtual bool endOfData() const = 0; + + /** + * End of stream reached? If this returns true, it means that all data + * in this stream is used up and no additional data will appear in it + * in the future. + * This is used by the mixer to decide whether a given stream shall be + * removed from the list of active streams (and thus be destroyed). + * By default this maps to endOfData() + */ + virtual bool endOfStream() const { return endOfData(); } +}; + +/** + * A rewindable audio stream. This allows for reseting the AudioStream + * to its initial state. Note that rewinding itself is not required to + * be working when the stream is being played by Mixer! + */ +class RewindableAudioStream : public AudioStream { +public: + /** + * Rewinds the stream to its start. + * + * @return true on success, false otherwise. + */ + virtual bool rewind() = 0; +}; + +/** + * A looping audio stream. This object does nothing besides using + * a RewindableAudioStream to play a stream in a loop. + */ +class LoopingAudioStream : public AudioStream { +public: + /** + * Creates a looping audio stream object. + * + * Note that on creation of the LoopingAudioStream object + * the underlying stream will be rewound. + * + * @see makeLoopingAudioStream + * + * @param stream Stream to loop + * @param loops How often to loop (0 = infinite) + * @param disposeAfterUse Destroy the stream after the LoopingAudioStream has finished playback. + */ + LoopingAudioStream(RewindableAudioStream *stream, uint loops, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + ~LoopingAudioStream(); + + int readBuffer(int16 *buffer, const int numSamples); + bool endOfData() const; + + bool isStereo() const { return _parent->isStereo(); } + int getRate() const { return _parent->getRate(); } + + /** + * Returns number of loops the stream has played. + * + * @param numLoops number of loops to play, 0 - infinite + */ + uint getCompleteIterations() const { return _completeIterations; } +private: + RewindableAudioStream *_parent; + DisposeAfterUse::Flag _disposeAfterUse; + + uint _loops; + uint _completeIterations; +}; + +/** + * Wrapper functionality to efficiently create a stream, which might be looped. + * + * Note that this function does not return a LoopingAudioStream, because it does + * not create one when the loop count is "1". This allows to keep the runtime + * overhead down, when the code does not require any functionality only offered + * by LoopingAudioStream. + * + * @param stream Stream to loop (will be automatically destroyed, when the looping is done) + * @param loops How often to loop (0 = infinite) + * @return A new AudioStream, which offers the desired functionality. + */ +AudioStream *makeLoopingAudioStream(RewindableAudioStream *stream, uint loops); + +/** + * A seekable audio stream. Subclasses of this class implement an + * interface for seeking. The seeking itself is not required to be + * working while the stream is being played by Mixer! + */ +class SeekableAudioStream : public RewindableAudioStream { +public: + /** + * Tries to load a file by trying all available formats. + * In case of an error, the file handle will be closed, but deleting + * it is still the responsibility of the caller. + * + * @param basename a filename without an extension + * @return an SeekableAudioStream ready to use in case of success; + * NULL in case of an error (e.g. invalid/nonexisting file) + */ + static SeekableAudioStream *openStreamFile(const Common::String &basename); + + /** + * Seeks to a given offset in the stream. + * + * @param where offset in milliseconds + * @return true on success, false on failure. + */ + bool seek(uint32 where) { + return seek(Timestamp(where, getRate())); + } + + /** + * Seeks to a given offset in the stream. + * + * @param where offset as timestamp + * @return true on success, false on failure. + */ + virtual bool seek(const Timestamp &where) = 0; + + /** + * Returns the length of the stream. + * + * @return length as Timestamp. + */ + virtual Timestamp getLength() const = 0; + + virtual bool rewind() { return seek(0); } +}; + +/** + * Wrapper functionality to efficiently create a stream, which might be looped + * in a certain interval. + * + * This automatically starts the stream at time "start"! + * + * Note that this function does not return a LoopingAudioStream, because it does + * not create one when the loop count is "1". This allows to keep the runtime + * overhead down, when the code does not require any functionality only offered + * by LoopingAudioStream. + * + * @param stream Stream to loop (will be automatically destroyed, when the looping is done) + * @param start Starttime of the stream interval to be looped + * @param end End of the stream interval to be looped (a zero time, means till end) + * @param loops How often to loop (0 = infinite) + * @return A new AudioStream, which offers the desired functionality. + */ +AudioStream *makeLoopingAudioStream(SeekableAudioStream *stream, Timestamp start, Timestamp end, uint loops); + +/** + * A looping audio stream, which features looping of a nested part of the + * stream. + * + * NOTE: + * Currently this implementation stops after the nested loop finished + * playback. + * + * IMPORTANT: + * This might be merged with SubSeekableAudioStream for playback purposes. + * (After extending it to accept a start time). + */ +class SubLoopingAudioStream : public AudioStream { +public: + /** + * Constructor for a SubLoopingAudioStream. + * + * Note that on creation of the SubLoopingAudioStream object + * the underlying stream will be rewound. + * + * @param stream Stream to loop + * @param loops How often the stream should be looped (0 means infinite) + * @param loopStart Start of the loop (this must be smaller than loopEnd) + * @param loopEnd End of the loop (thus must be greater than loopStart) + * @param disposeAfterUse Whether the stream should be disposed, when the + * SubLoopingAudioStream is destroyed. + */ + SubLoopingAudioStream(SeekableAudioStream *stream, uint loops, + const Timestamp loopStart, + const Timestamp loopEnd, + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + ~SubLoopingAudioStream(); + + int readBuffer(int16 *buffer, const int numSamples); + bool endOfData() const { return _done; } + + bool isStereo() const { return _parent->isStereo(); } + int getRate() const { return _parent->getRate(); } +private: + SeekableAudioStream *_parent; + DisposeAfterUse::Flag _disposeAfterUse; + + uint _loops; + Timestamp _pos; + Timestamp _loopStart, _loopEnd; + + bool _done; +}; + + +/** + * A SubSeekableAudioStream provides access to a SeekableAudioStream + * just in the range [start, end). + * The same caveats apply to SubSeekableAudioStream as do to SeekableAudioStream. + * + * Manipulating the parent stream directly /will/ mess up a substream. + */ +class SubSeekableAudioStream : public SeekableAudioStream { +public: + /** + * Creates a new SubSeekableAudioStream. + * + * @param parent parent stream object. + * @param start Start time. + * @param end End time. + * @param disposeAfterUse Whether the parent stream object should be destroyed on destruction of the SubSeekableAudioStream. + */ + SubSeekableAudioStream(SeekableAudioStream *parent, const Timestamp start, const Timestamp end, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + ~SubSeekableAudioStream(); + + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { return _parent->isStereo(); } + + int getRate() const { return _parent->getRate(); } + + bool endOfData() const { return (_pos >= _length) || _parent->endOfStream(); } + + bool seek(const Timestamp &where); + + Timestamp getLength() const { return _length; } +private: + SeekableAudioStream *_parent; + DisposeAfterUse::Flag _disposeAfterUse; + + const Timestamp _start; + const Timestamp _length; + Timestamp _pos; +}; + +class QueuingAudioStream : public Audio::AudioStream { +public: + + /** + * Queue an audio stream for playback. This stream plays all queued + * streams, in the order they were queued. If disposeAfterUse is set to + * DisposeAfterUse::YES, then the queued stream is deleted after all data + * contained in it has been played. + */ + virtual void queueAudioStream(Audio::AudioStream *audStream, + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES) = 0; + + /** + * Queue a block of raw audio data for playback. This stream plays all + * queued block, in the order they were queued. If disposeAfterUse is set + * to DisposeAfterUse::YES, then the queued block is released using free() + * after all data contained in it has been played. + * + * @note Make sure to allocate the data block with malloc(), not with new[]. + * + * @param data pointer to the audio data block + * @param size length of the audio data block + * @param disposeAfterUse if equal to DisposeAfterUse::YES, the block is released using free() after use. + * @param flags a bit-ORed combination of RawFlags describing the audio data format + */ + void queueBuffer(byte *data, uint32 size, DisposeAfterUse::Flag disposeAfterUse, byte flags); + + /** + * Mark this stream as finished. That is, signal that no further data + * will be queued to it. Only after this has been done can this + * stream ever 'end'. + */ + virtual void finish() = 0; + + /** + * Return the number of streams still queued for playback (including + * the currently playing stream). + */ + virtual uint32 numQueuedStreams() const = 0; +}; + +/** + * Factory function for an QueuingAudioStream. + */ +QueuingAudioStream *makeQueuingAudioStream(int rate, bool stereo); + +/** + * Converts a point in time to a precise sample offset + * with the given parameters. + * + * @param where Point in time. + * @param rate Rate of the stream. + * @param isStereo Is the stream a stereo stream? + */ +Timestamp convertTimeToStreamPos(const Timestamp &where, int rate, bool isStereo); + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/adpcm.cpp b/audio/decoders/adpcm.cpp new file mode 100644 index 0000000000..7def89b688 --- /dev/null +++ b/audio/decoders/adpcm.cpp @@ -0,0 +1,851 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/endian.h" + +#include "audio/decoders/adpcm.h" +#include "audio/audiostream.h" + + +namespace Audio { + +class ADPCMStream : public RewindableAudioStream { +protected: + Common::SeekableReadStream *_stream; + const DisposeAfterUse::Flag _disposeAfterUse; + const int32 _startpos; + const int32 _endpos; + const int _channels; + const uint32 _blockAlign; + uint32 _blockPos[2]; + const int _rate; + + struct { + // OKI/IMA + struct { + int32 last; + int32 stepIndex; + } ima_ch[2]; + } _status; + + virtual void reset(); + int16 stepAdjust(byte); + +public: + ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign); + ~ADPCMStream(); + + virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos); } + virtual bool isStereo() const { return _channels == 2; } + virtual int getRate() const { return _rate; } + + virtual bool rewind(); +}; + +// Routines to convert 12 bit linear samples to the +// Dialogic or Oki ADPCM coding format aka VOX. +// See also +// +// IMA ADPCM support is based on +// +// +// In addition, also MS IMA ADPCM is supported. See +// . + +ADPCMStream::ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : _stream(stream), + _disposeAfterUse(disposeAfterUse), + _startpos(stream->pos()), + _endpos(_startpos + size), + _channels(channels), + _blockAlign(blockAlign), + _rate(rate) { + + reset(); +} + +ADPCMStream::~ADPCMStream() { + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _stream; +} + +void ADPCMStream::reset() { + memset(&_status, 0, sizeof(_status)); + _blockPos[0] = _blockPos[1] = _blockAlign; // To make sure first header is read +} + +bool ADPCMStream::rewind() { + // TODO: Error checking. + reset(); + _stream->seek(_startpos); + return true; +} + + +#pragma mark - + + +class Oki_ADPCMStream : public ADPCMStream { +public: + Oki_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {} + + virtual int readBuffer(int16 *buffer, const int numSamples); + +protected: + int16 decodeOKI(byte); +}; + +int Oki_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples; + byte data; + + assert(numSamples % 2 == 0); + + for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { + data = _stream->readByte(); + buffer[samples] = decodeOKI((data >> 4) & 0x0f); + buffer[samples + 1] = decodeOKI(data & 0x0f); + } + return samples; +} + +static const int16 okiStepSize[49] = { + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552 +}; + +// Decode Linear to ADPCM +int16 Oki_ADPCMStream::decodeOKI(byte code) { + int16 diff, E, samp; + + E = (2 * (code & 0x7) + 1) * okiStepSize[_status.ima_ch[0].stepIndex] / 8; + diff = (code & 0x08) ? -E : E; + samp = _status.ima_ch[0].last + diff; + // Clip the values to +/- 2^11 (supposed to be 12 bits) + samp = CLIP(samp, -2048, 2047); + + _status.ima_ch[0].last = samp; + _status.ima_ch[0].stepIndex += stepAdjust(code); + _status.ima_ch[0].stepIndex = CLIP(_status.ima_ch[0].stepIndex, 0, ARRAYSIZE(okiStepSize) - 1); + + // * 16 effectively converts 12-bit input to 16-bit output + return samp * 16; +} + + +#pragma mark - + + +class Ima_ADPCMStream : public ADPCMStream { +protected: + int16 decodeIMA(byte code, int channel = 0); // Default to using the left channel/using one channel + +public: + Ima_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { + memset(&_status, 0, sizeof(_status)); + } + + virtual int readBuffer(int16 *buffer, const int numSamples); +}; + +int Ima_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples; + byte data; + + assert(numSamples % 2 == 0); + + for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { + data = _stream->readByte(); + buffer[samples] = decodeIMA((data >> 4) & 0x0f); + buffer[samples + 1] = decodeIMA(data & 0x0f, _channels == 2 ? 1 : 0); + } + return samples; +} + +#pragma mark - + + +class Apple_ADPCMStream : public Ima_ADPCMStream { +protected: + // Apple QuickTime IMA ADPCM + int32 _streamPos[2]; + int16 _buffer[2][2]; + uint8 _chunkPos[2]; + + void reset() { + Ima_ADPCMStream::reset(); + _chunkPos[0] = 0; + _chunkPos[1] = 0; + _streamPos[0] = 0; + _streamPos[1] = _blockAlign; + } + +public: + Apple_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { + _chunkPos[0] = 0; + _chunkPos[1] = 0; + _streamPos[0] = 0; + _streamPos[1] = _blockAlign; + } + + virtual int readBuffer(int16 *buffer, const int numSamples); + +}; + +int Apple_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + // Need to write at least one samples per channel + assert((numSamples % _channels) == 0); + + // Current sample positions + int samples[2] = { 0, 0}; + + // Number of samples per channel + int chanSamples = numSamples / _channels; + + for (int i = 0; i < _channels; i++) { + _stream->seek(_streamPos[i]); + + while ((samples[i] < chanSamples) && + // Last byte read and a new one needed + !((_stream->eos() || (_stream->pos() >= _endpos)) && (_chunkPos[i] == 0))) { + + if (_blockPos[i] == _blockAlign) { + // 2 byte header per block + uint16 temp = _stream->readUint16BE(); + + // First 9 bits are the upper bits of the predictor + _status.ima_ch[i].last = (int16) (temp & 0xFF80); + // Lower 7 bits are the step index + _status.ima_ch[i].stepIndex = temp & 0x007F; + + // Clip the step index + _status.ima_ch[i].stepIndex = CLIP(_status.ima_ch[i].stepIndex, 0, 88); + + _blockPos[i] = 2; + } + + if (_chunkPos[i] == 0) { + // Decode data + byte data = _stream->readByte(); + _buffer[i][0] = decodeIMA(data & 0x0F, i); + _buffer[i][1] = decodeIMA(data >> 4, i); + } + + // The original is interleaved block-wise, we want it sample-wise + buffer[_channels * samples[i] + i] = _buffer[i][_chunkPos[i]]; + + if (++_chunkPos[i] > 1) { + // We're about to decode the next byte, so advance the block position + _chunkPos[i] = 0; + _blockPos[i]++; + } + + samples[i]++; + + if (_channels == 2) + if (_blockPos[i] == _blockAlign) + // We're at the end of the block. + // Since the channels are interleaved, skip the next block + _stream->skip(MIN(_blockAlign, _endpos - _stream->pos())); + + _streamPos[i] = _stream->pos(); + } + } + + return samples[0] + samples[1]; +} + +#pragma mark - + + +class MSIma_ADPCMStream : public Ima_ADPCMStream { +public: + MSIma_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign, bool invertSamples = false) + : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign), _invertSamples(invertSamples) { + if (blockAlign == 0) + error("ADPCMStream(): blockAlign isn't specified for MS IMA ADPCM"); + } + + virtual int readBuffer(int16 *buffer, const int numSamples) { + if (_channels == 1) + return readBufferMSIMA1(buffer, numSamples); + else + return readBufferMSIMA2(buffer, numSamples); + } + + int readBufferMSIMA1(int16 *buffer, const int numSamples); + int readBufferMSIMA2(int16 *buffer, const int numSamples); + +private: + bool _invertSamples; // Some implementations invert the way samples are decoded +}; + +int MSIma_ADPCMStream::readBufferMSIMA1(int16 *buffer, const int numSamples) { + int samples = 0; + byte data; + + assert(numSamples % 2 == 0); + + while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { + if (_blockPos[0] == _blockAlign) { + // read block header + _status.ima_ch[0].last = _stream->readSint16LE(); + _status.ima_ch[0].stepIndex = _stream->readSint16LE(); + _blockPos[0] = 4; + } + + for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { + data = _stream->readByte(); + _blockPos[0]++; + buffer[samples] = decodeIMA(_invertSamples ? (data >> 4) & 0x0f : data & 0x0f); + buffer[samples + 1] = decodeIMA(_invertSamples ? data & 0x0f : (data >> 4) & 0x0f); + } + } + return samples; +} + + +// Microsoft as usual tries to implement it differently. This method +// is used for stereo data. +int MSIma_ADPCMStream::readBufferMSIMA2(int16 *buffer, const int numSamples) { + int samples; + uint32 data; + int nibble; + byte k; + + // TODO: Currently this implementation only supports + // reading a multiple of 16 samples at once. We might + // consider changing that so it could read an arbitrary + // sample pair count. + assert(numSamples % 16 == 0); + + for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos;) { + for (int channel = 0; channel < 2; channel++) { + data = _stream->readUint32LE(); + + for (nibble = 0; nibble < 8; nibble++) { + k = ((data & 0xf0000000) >> 28); + buffer[samples + channel + nibble * 2] = decodeIMA(k); + data <<= 4; + } + } + samples += 16; + } + return samples; +} + + +#pragma mark - + + +static const int MSADPCMAdaptCoeff1[] = { + 256, 512, 0, 192, 240, 460, 392 +}; + +static const int MSADPCMAdaptCoeff2[] = { + 0, -256, 0, 64, 0, -208, -232 +}; + +static const int MSADPCMAdaptationTable[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 +}; + + +class MS_ADPCMStream : public ADPCMStream { +protected: + struct ADPCMChannelStatus { + byte predictor; + int16 delta; + int16 coeff1; + int16 coeff2; + int16 sample1; + int16 sample2; + }; + + struct { + // MS ADPCM + ADPCMChannelStatus ch[2]; + } _status; + + void reset() { + ADPCMStream::reset(); + memset(&_status, 0, sizeof(_status)); + } + +public: + MS_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { + if (blockAlign == 0) + error("MS_ADPCMStream(): blockAlign isn't specified for MS ADPCM"); + memset(&_status, 0, sizeof(_status)); + } + + virtual int readBuffer(int16 *buffer, const int numSamples); + +protected: + int16 decodeMS(ADPCMChannelStatus *c, byte); +}; + +int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) { + int32 predictor; + + predictor = (((c->sample1) * (c->coeff1)) + ((c->sample2) * (c->coeff2))) / 256; + predictor += (signed)((code & 0x08) ? (code - 0x10) : (code)) * c->delta; + + predictor = CLIP(predictor, -32768, 32767); + + c->sample2 = c->sample1; + c->sample1 = predictor; + c->delta = (MSADPCMAdaptationTable[(int)code] * c->delta) >> 8; + + if (c->delta < 16) + c->delta = 16; + + return (int16)predictor; +} + +int MS_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples; + byte data; + int i = 0; + + samples = 0; + + while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { + if (_blockPos[0] == _blockAlign) { + // read block header + for (i = 0; i < _channels; i++) { + _status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6); + _status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor]; + _status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor]; + } + + for (i = 0; i < _channels; i++) + _status.ch[i].delta = _stream->readSint16LE(); + + for (i = 0; i < _channels; i++) + _status.ch[i].sample1 = _stream->readSint16LE(); + + for (i = 0; i < _channels; i++) + buffer[samples++] = _status.ch[i].sample2 = _stream->readSint16LE(); + + for (i = 0; i < _channels; i++) + buffer[samples++] = _status.ch[i].sample1; + + _blockPos[0] = _channels * 7; + } + + for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { + data = _stream->readByte(); + _blockPos[0]++; + buffer[samples] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f); + buffer[samples + 1] = decodeMS(&_status.ch[_channels - 1], data & 0x0f); + } + } + + return samples; +} + + + +#pragma mark - + + +class Tinsel_ADPCMStream : public ADPCMStream { +protected: + struct { + // Tinsel + double predictor; + double K0, K1; + double d0, d1; + } _status; + + void reset() { + ADPCMStream::reset(); + memset(&_status, 0, sizeof(_status)); + } + + int16 decodeTinsel(int16, double); + void readBufferTinselHeader(); + +public: + Tinsel_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { + + if (blockAlign == 0) + error("Tinsel_ADPCMStream(): blockAlign isn't specified"); + + if (channels != 1) + error("Tinsel_ADPCMStream(): Tinsel ADPCM only supports mono"); + + memset(&_status, 0, sizeof(_status)); + } + +}; + +static const double TinselFilterTable[4][2] = { + {0, 0 }, + {0.9375, 0}, + {1.796875, -0.8125}, + {1.53125, -0.859375} +}; + +void Tinsel_ADPCMStream::readBufferTinselHeader() { + uint8 start = _stream->readByte(); + uint8 filterVal = (start & 0xC0) >> 6; + + if ((start & 0x20) != 0) { + //Lower 6 bit are negative + + // Negate + start = ~(start | 0xC0) + 1; + + _status.predictor = 1 << start; + } else { + // Lower 6 bit are positive + + // Truncate + start &= 0x1F; + + _status.predictor = ((double) 1.0) / (1 << start); + } + + _status.K0 = TinselFilterTable[filterVal][0]; + _status.K1 = TinselFilterTable[filterVal][1]; +} + +int16 Tinsel_ADPCMStream::decodeTinsel(int16 code, double eVal) { + double sample; + + sample = (double) code; + sample *= eVal * _status.predictor; + sample += (_status.d0 * _status.K0) + (_status.d1 * _status.K1); + + _status.d1 = _status.d0; + _status.d0 = sample; + + return (int16) CLIP(sample, -32768.0, 32767.0); +} + +class Tinsel4_ADPCMStream : public Tinsel_ADPCMStream { +public: + Tinsel4_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {} + + virtual int readBuffer(int16 *buffer, const int numSamples); +}; + +int Tinsel4_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples; + uint16 data; + const double eVal = 1.142822265; + + samples = 0; + + assert(numSamples % 2 == 0); + + while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { + if (_blockPos[0] == _blockAlign) { + readBufferTinselHeader(); + _blockPos[0] = 0; + } + + for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2, _blockPos[0]++) { + // Read 1 byte = 8 bits = two 4 bit blocks + data = _stream->readByte(); + buffer[samples] = decodeTinsel((data << 8) & 0xF000, eVal); + buffer[samples+1] = decodeTinsel((data << 12) & 0xF000, eVal); + } + } + + return samples; +} + +class Tinsel6_ADPCMStream : public Tinsel_ADPCMStream { +protected: + uint8 _chunkPos; + uint16 _chunkData; + + void reset() { + ADPCMStream::reset(); + _chunkPos = 0; + _chunkData = 0; + } + +public: + Tinsel6_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { + _chunkPos = 0; + _chunkData = 0; + } + + virtual int readBuffer(int16 *buffer, const int numSamples); +}; + +int Tinsel6_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples; + const double eVal = 1.032226562; + + samples = 0; + + while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { + if (_blockPos[0] == _blockAlign) { + readBufferTinselHeader(); + _blockPos[0] = 0; + _chunkPos = 0; + } + + for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples++, _chunkPos = (_chunkPos + 1) % 4) { + + switch (_chunkPos) { + case 0: + _chunkData = _stream->readByte(); + buffer[samples] = decodeTinsel((_chunkData << 8) & 0xFC00, eVal); + break; + case 1: + _chunkData = (_chunkData << 8) | (_stream->readByte()); + buffer[samples] = decodeTinsel((_chunkData << 6) & 0xFC00, eVal); + _blockPos[0]++; + break; + case 2: + _chunkData = (_chunkData << 8) | (_stream->readByte()); + buffer[samples] = decodeTinsel((_chunkData << 4) & 0xFC00, eVal); + _blockPos[0]++; + break; + case 3: + _chunkData = (_chunkData << 8); + buffer[samples] = decodeTinsel((_chunkData << 2) & 0xFC00, eVal); + _blockPos[0]++; + break; + } + + } + + } + + return samples; +} + +class Tinsel8_ADPCMStream : public Tinsel_ADPCMStream { +public: + Tinsel8_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {} + + virtual int readBuffer(int16 *buffer, const int numSamples); +}; + +int Tinsel8_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples; + byte data; + const double eVal = 1.007843258; + + samples = 0; + + while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { + if (_blockPos[0] == _blockAlign) { + readBufferTinselHeader(); + _blockPos[0] = 0; + } + + for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples++, _blockPos[0]++) { + // Read 1 byte = 8 bits = one 8 bit block + data = _stream->readByte(); + buffer[samples] = decodeTinsel(data << 8, eVal); + } + } + + return samples; +} + + +#pragma mark - + +// Duck DK3 IMA ADPCM Decoder +// Based on FFmpeg's decoder and http://wiki.multimedia.cx/index.php?title=Duck_DK3_IMA_ADPCM + +class DK3_ADPCMStream : public Ima_ADPCMStream { +protected: + + void reset() { + Ima_ADPCMStream::reset(); + _topNibble = false; + } + +public: + DK3_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { + + // DK3 only works as a stereo stream + assert(channels == 2); + _topNibble = false; + } + + virtual int readBuffer(int16 *buffer, const int numSamples); + +private: + byte _nibble, _lastByte; + bool _topNibble; +}; + +#define DK3_READ_NIBBLE() \ +do { \ + if (_topNibble) { \ + _nibble = _lastByte >> 4; \ + _topNibble = false; \ + } else { \ + if (_stream->pos() >= _endpos) \ + break; \ + if ((_stream->pos() % _blockAlign) == 0) \ + continue; \ + _lastByte = _stream->readByte(); \ + _nibble = _lastByte & 0xf; \ + _topNibble = true; \ + } \ +} while (0) + + +int DK3_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples = 0; + + assert((numSamples % 4) == 0); + + while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { + if ((_stream->pos() % _blockAlign) == 0) { + _stream->readUint16LE(); // Unknown + uint16 rate = _stream->readUint16LE(); // Copy of rate + _stream->skip(6); // Unknown + // Get predictor for both sum/diff channels + _status.ima_ch[0].last = _stream->readSint16LE(); + _status.ima_ch[1].last = _stream->readSint16LE(); + // Get index for both sum/diff channels + _status.ima_ch[0].stepIndex = _stream->readByte(); + _status.ima_ch[1].stepIndex = _stream->readByte(); + + if (_stream->eos()) + break; + + // Sanity check + assert(rate == getRate()); + } + + DK3_READ_NIBBLE(); + decodeIMA(_nibble, 0); + + DK3_READ_NIBBLE(); + decodeIMA(_nibble, 1); + + buffer[samples++] = _status.ima_ch[0].last + _status.ima_ch[1].last; + buffer[samples++] = _status.ima_ch[0].last - _status.ima_ch[1].last; + + DK3_READ_NIBBLE(); + decodeIMA(_nibble, 0); + + buffer[samples++] = _status.ima_ch[0].last + _status.ima_ch[1].last; + buffer[samples++] = _status.ima_ch[0].last - _status.ima_ch[1].last; + } + + return samples; +} + + +#pragma mark - + + +// adjust the step for use on the next sample. +int16 ADPCMStream::stepAdjust(byte code) { + static const int16 adjusts[] = {-1, -1, -1, -1, 2, 4, 6, 8}; + + return adjusts[code & 0x07]; +} + +static const uint16 imaStepTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, + 7132, 7845, 8630, 9493,10442,11487,12635,13899, + 15289,16818,18500,20350,22385,24623,27086,29794, + 32767 +}; + +int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) { + int32 E = (2 * (code & 0x7) + 1) * imaStepTable[_status.ima_ch[channel].stepIndex] / 8; + int32 diff = (code & 0x08) ? -E : E; + int32 samp = CLIP(_status.ima_ch[channel].last + diff, -32768, 32767); + + _status.ima_ch[channel].last = samp; + _status.ima_ch[channel].stepIndex += stepAdjust(code); + _status.ima_ch[channel].stepIndex = CLIP(_status.ima_ch[channel].stepIndex, 0, ARRAYSIZE(imaStepTable) - 1); + + return samp; +} + +RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) { + // If size is 0, report the entire size of the stream + if (!size) + size = stream->size(); + + switch (type) { + case kADPCMOki: + return new Oki_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMMSIma: + return new MSIma_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMMSImaLastExpress: + return new MSIma_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign, true); + case kADPCMMS: + return new MS_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMTinsel4: + return new Tinsel4_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMTinsel6: + return new Tinsel6_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMTinsel8: + return new Tinsel8_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMIma: + return new Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMApple: + return new Apple_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMDK3: + return new DK3_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + default: + error("Unsupported ADPCM encoding"); + break; + } +} + +} // End of namespace Audio diff --git a/audio/decoders/adpcm.h b/audio/decoders/adpcm.h new file mode 100644 index 0000000000..38ec870a27 --- /dev/null +++ b/audio/decoders/adpcm.h @@ -0,0 +1,90 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - agos + * - lastexpress + * - mohawk + * - saga + * - scumm + * - tinsel + */ + +#ifndef SOUND_ADPCM_H +#define SOUND_ADPCM_H + +#include "common/scummsys.h" +#include "common/stream.h" + + +namespace Audio { + +class AudioStream; +class RewindableAudioStream; + +// There are several types of ADPCM encoding, only some are supported here +// For all the different encodings, refer to: +// http://wiki.multimedia.cx/index.php?title=Category:ADPCM_Audio_Codecs +// Usually, if the audio stream we're trying to play has the FourCC header +// string intact, it's easy to discern which encoding is used +enum typesADPCM { + kADPCMOki, // Dialogic/Oki ADPCM (aka VOX) + kADPCMMSIma, // Microsoft IMA ADPCM + kADPCMMSImaLastExpress, // Microsoft IMA ADPCM (with inverted samples) + kADPCMMS, // Microsoft ADPCM + kADPCMTinsel4, // 4-bit ADPCM used by the Tinsel engine + kADPCMTinsel6, // 6-bit ADPCM used by the Tinsel engine + kADPCMTinsel8, // 8-bit ADPCM used by the Tinsel engine + kADPCMIma, // Standard IMA ADPCM + kADPCMApple, // Apple QuickTime IMA ADPCM + kADPCMDK3 // Duck DK3 IMA ADPCM +}; + +/** + * Takes an input stream containing ADPCM compressed sound data and creates + * an RewindableAudioStream from that. + * + * @param stream the SeekableReadStream from which to read the ADPCM data + * @param disposeAfterUse whether to delete the stream after use + * @param size how many bytes to read from the stream (0 = all) + * @param type the compression type used + * @param rate the sampling rate + * @param channels the number of channels + * @param blockAlign block alignment ??? + * @return a new RewindableAudioStream, or NULL, if an error occurred + */ +RewindableAudioStream *makeADPCMStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse, + uint32 size, typesADPCM type, + int rate = 22050, + int channels = 2, + uint32 blockAlign = 0); + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/aiff.cpp b/audio/decoders/aiff.cpp new file mode 100644 index 0000000000..0f947752f6 --- /dev/null +++ b/audio/decoders/aiff.cpp @@ -0,0 +1,186 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * The code in this file is based on information found at + * http://www.borg.com/~jglatt/tech/aiff.htm + * + * We currently only implement uncompressed AIFF. If we ever need AIFF-C, SoX + * (http://sox.sourceforge.net) may be a good place to start from. + */ + +#include "common/endian.h" +#include "common/util.h" +#include "common/stream.h" + +#include "audio/decoders/aiff.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" + +namespace Audio { + +uint32 readExtended(Common::SeekableReadStream &stream) { + // The sample rate is stored as an "80 bit IEEE Standard 754 floating + // point number (Standard Apple Numeric Environment [SANE] data type + // Extended). + + byte buf[10]; + uint32 mantissa; + uint32 last = 0; + byte exp; + + stream.read(buf, 10); + mantissa = READ_BE_UINT32(buf + 2); + exp = 30 - buf[1]; + + while (exp--) { + last = mantissa; + mantissa >>= 1; + } + + if (last & 0x00000001) + mantissa++; + + return mantissa; +} + +bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags) { + byte buf[4]; + + stream.read(buf, 4); + if (memcmp(buf, "FORM", 4) != 0) { + warning("loadAIFFFromStream: No 'FORM' header"); + return false; + } + + stream.readUint32BE(); + + // This could be AIFC, but we don't handle that case. + + stream.read(buf, 4); + if (memcmp(buf, "AIFF", 4) != 0) { + warning("loadAIFFFromStream: No 'AIFF' header"); + return false; + } + + // From here on, we only care about the COMM and SSND chunks, which are + // the only required chunks. + + bool foundCOMM = false; + bool foundSSND = false; + + uint16 numChannels = 0, bitsPerSample = 0; + uint32 numSampleFrames = 0, offset = 0, blockSize = 0, soundOffset = 0; + + while (!(foundCOMM && foundSSND) && !stream.err() && !stream.eos()) { + uint32 length, pos; + + stream.read(buf, 4); + length = stream.readUint32BE(); + pos = stream.pos(); + + if (memcmp(buf, "COMM", 4) == 0) { + foundCOMM = true; + numChannels = stream.readUint16BE(); + numSampleFrames = stream.readUint32BE(); + bitsPerSample = stream.readUint16BE(); + rate = readExtended(stream); + size = numSampleFrames * numChannels * (bitsPerSample / 8); + } else if (memcmp(buf, "SSND", 4) == 0) { + foundSSND = true; + offset = stream.readUint32BE(); + blockSize = stream.readUint32BE(); + soundOffset = stream.pos(); + } + + stream.seek(pos + length); + } + + if (!foundCOMM) { + warning("loadAIFFFromStream: Cound not find 'COMM' chunk"); + return false; + } + + if (!foundSSND) { + warning("loadAIFFFromStream: Cound not find 'SSND' chunk"); + return false; + } + + // We only implement a subset of the AIFF standard. + + if (numChannels < 1 || numChannels > 2) { + warning("loadAIFFFromStream: Only 1 or 2 channels are supported, not %d", numChannels); + return false; + } + + if (bitsPerSample != 8 && bitsPerSample != 16) { + warning("loadAIFFFromStream: Only 8 or 16 bits per sample are supported, not %d", bitsPerSample); + return false; + } + + if (offset != 0 || blockSize != 0) { + warning("loadAIFFFromStream: Block-aligned data is not supported"); + return false; + } + + // Samples are always signed, and big endian. + + flags = 0; + if (bitsPerSample == 16) + flags |= Audio::FLAG_16BITS; + if (numChannels == 2) + flags |= Audio::FLAG_STEREO; + + stream.seek(soundOffset); + + // Stream now points at the sample data + + return true; +} + +SeekableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse) { + int size, rate; + byte *data, flags; + + if (!loadAIFFFromStream(*stream, size, rate, flags)) { + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + return 0; + } + + data = (byte *)malloc(size); + assert(data); + stream->read(data, size); + + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + + // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. + return makeRawStream(data, size, rate, flags); +} + +} // End of namespace Audio diff --git a/audio/decoders/aiff.h b/audio/decoders/aiff.h new file mode 100644 index 0000000000..06c56ecd38 --- /dev/null +++ b/audio/decoders/aiff.h @@ -0,0 +1,71 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - saga + * - sci + * - sword1 + */ + +#ifndef SOUND_AIFF_H +#define SOUND_AIFF_H + +#include "common/scummsys.h" +#include "common/types.h" + +namespace Common { class SeekableReadStream; } + +namespace Audio { + +class SeekableAudioStream; + +/** + * Try to load an AIFF from the given seekable stream. Returns true if + * successful. In that case, the stream's seek position will be set to the + * start of the audio data, and size, rate and flags contain information + * necessary for playback. Currently this function only supports uncompressed + * raw PCM data as well as IMA ADPCM. + */ +extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags); + +/** + * Try to load an AIFF from the given seekable stream and create an AudioStream + * from that data. + * + * This function uses loadAIFFFromStream() internally. + * + * @param stream the SeekableReadStream from which to read the AIFF data + * @param disposeAfterUse whether to delete the stream after use + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *makeAIFFStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/flac.cpp b/audio/decoders/flac.cpp new file mode 100644 index 0000000000..76b6d35419 --- /dev/null +++ b/audio/decoders/flac.cpp @@ -0,0 +1,745 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Disable symbol overrides for FILE as that is used in FLAC headers +#define FORBIDDEN_SYMBOL_EXCEPTION_FILE + +#include "audio/decoders/flac.h" + +#ifdef USE_FLAC + +#include "common/debug.h" +#include "common/stream.h" +#include "common/util.h" + +#include "audio/audiostream.h" + +#define FLAC__NO_DLL // that MS-magic gave me headaches - just link the library you like +#include + + +// check if we have FLAC >= 1.1.3; LEGACY_FLAC code can be removed once FLAC-1.1.3 propagates everywhere +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT < 8 +#define LEGACY_FLAC +#else +#undef LEGACY_FLAC +#endif + + +#ifdef LEGACY_FLAC + +// Before FLAC 1.1.3, we needed to use the stream decoder API. +#include +typedef uint FLAC_size_t; + +#else + +// With FLAC 1.1.3, the stream decoder API was merged into the regular +// stream API. In order to stay compatible with older FLAC versions, we +// simply add some typedefs and #ifdefs to map between the old and new API. +// We use the typedefs (instead of only #defines) in order to somewhat +// improve the readability of the code. + +#include +typedef size_t FLAC_size_t; +// Add aliases for the old names +typedef FLAC__StreamDecoderState FLAC__SeekableStreamDecoderState; +typedef FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus; +typedef FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus; +typedef FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus; +typedef FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus; +typedef FLAC__StreamDecoder FLAC__SeekableStreamDecoder; + +#endif + + +namespace Audio { + +#pragma mark - +#pragma mark --- FLAC stream --- +#pragma mark - + +static const uint MAX_OUTPUT_CHANNELS = 2; + + +class FLACStream : public SeekableAudioStream { +protected: + Common::SeekableReadStream *_inStream; + bool _disposeAfterUse; + + ::FLAC__SeekableStreamDecoder *_decoder; + + /** Header of the stream */ + FLAC__StreamMetadata_StreamInfo _streaminfo; + + /** index + 1(!) of the last sample to be played */ + FLAC__uint64 _lastSample; + + /** total play time */ + Timestamp _length; + + /** true if the last sample was decoded from the FLAC-API - there might still be data in the buffer */ + bool _lastSampleWritten; + + typedef int16 SampleType; + enum { BUFTYPE_BITS = 16 }; + + enum { + // Maximal buffer size. According to the FLAC format specification, the block size is + // a 16 bit value (in fact it seems the maximal block size is 32768, but we play it safe). + BUFFER_SIZE = 65536 + }; + + struct { + SampleType bufData[BUFFER_SIZE]; + SampleType *bufReadPos; + uint bufFill; + } _sampleCache; + + SampleType *_outBuffer; + uint _requestedSamples; + + typedef void (*PFCONVERTBUFFERS)(SampleType*, const FLAC__int32*[], uint, const uint, const uint8); + PFCONVERTBUFFERS _methodConvertBuffers; + + +public: + FLACStream(Common::SeekableReadStream *inStream, bool dispose); + virtual ~FLACStream(); + + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { return _streaminfo.channels >= 2; } + int getRate() const { return _streaminfo.sample_rate; } + bool endOfData() const { + // End of data is reached if there either is no valid stream data available, + // or if we reached the last sample and completely emptied the sample cache. + return _streaminfo.channels == 0 || (_lastSampleWritten && _sampleCache.bufFill == 0); + } + + bool seek(const Timestamp &where); + Timestamp getLength() const { return _length; } + + bool isStreamDecoderReady() const { return getStreamDecoderState() == FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC; } +protected: + uint getChannels() const { return MIN(_streaminfo.channels, MAX_OUTPUT_CHANNELS); } + + bool allocateBuffer(uint minSamples); + + inline FLAC__StreamDecoderState getStreamDecoderState() const; + + inline bool processSingleBlock(); + inline bool processUntilEndOfMetadata(); + bool seekAbsolute(FLAC__uint64 sample); + + inline ::FLAC__SeekableStreamDecoderReadStatus callbackRead(FLAC__byte buffer[], FLAC_size_t *bytes); + inline ::FLAC__SeekableStreamDecoderSeekStatus callbackSeek(FLAC__uint64 absoluteByteOffset); + inline ::FLAC__SeekableStreamDecoderTellStatus callbackTell(FLAC__uint64 *absoluteByteOffset); + inline ::FLAC__SeekableStreamDecoderLengthStatus callbackLength(FLAC__uint64 *streamLength); + inline bool callbackEOF(); + inline ::FLAC__StreamDecoderWriteStatus callbackWrite(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]); + inline void callbackMetadata(const ::FLAC__StreamMetadata *metadata); + inline void callbackError(::FLAC__StreamDecoderErrorStatus status); + +private: + static ::FLAC__SeekableStreamDecoderReadStatus callWrapRead(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], FLAC_size_t *bytes, void *clientData); + static ::FLAC__SeekableStreamDecoderSeekStatus callWrapSeek(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absoluteByteOffset, void *clientData); + static ::FLAC__SeekableStreamDecoderTellStatus callWrapTell(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absoluteByteOffset, void *clientData); + static ::FLAC__SeekableStreamDecoderLengthStatus callWrapLength(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *streamLength, void *clientData); + static FLAC__bool callWrapEOF(const ::FLAC__SeekableStreamDecoder *decoder, void *clientData); + static ::FLAC__StreamDecoderWriteStatus callWrapWrite(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *clientData); + static void callWrapMetadata(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__StreamMetadata *metadata, void *clientData); + static void callWrapError(const ::FLAC__SeekableStreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *clientData); + + void setBestConvertBufferMethod(); + static void convertBuffersGeneric(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); + static void convertBuffersStereoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); + static void convertBuffersStereo8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); + static void convertBuffersMonoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); + static void convertBuffersMono8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); +}; + +FLACStream::FLACStream(Common::SeekableReadStream *inStream, bool dispose) +#ifdef LEGACY_FLAC + : _decoder(::FLAC__seekable_stream_decoder_new()), +#else + : _decoder(::FLAC__stream_decoder_new()), +#endif + _inStream(inStream), + _disposeAfterUse(dispose), + _length(0, 1000), _lastSample(0), + _outBuffer(NULL), _requestedSamples(0), _lastSampleWritten(false), + _methodConvertBuffers(&FLACStream::convertBuffersGeneric) +{ + assert(_inStream); + memset(&_streaminfo, 0, sizeof(_streaminfo)); + + _sampleCache.bufReadPos = NULL; + _sampleCache.bufFill = 0; + + _methodConvertBuffers = &FLACStream::convertBuffersGeneric; + + bool success; +#ifdef LEGACY_FLAC + ::FLAC__seekable_stream_decoder_set_read_callback(_decoder, &FLACStream::callWrapRead); + ::FLAC__seekable_stream_decoder_set_seek_callback(_decoder, &FLACStream::callWrapSeek); + ::FLAC__seekable_stream_decoder_set_tell_callback(_decoder, &FLACStream::callWrapTell); + ::FLAC__seekable_stream_decoder_set_length_callback(_decoder, &FLACStream::callWrapLength); + ::FLAC__seekable_stream_decoder_set_eof_callback(_decoder, &FLACStream::callWrapEOF); + ::FLAC__seekable_stream_decoder_set_write_callback(_decoder, &FLACStream::callWrapWrite); + ::FLAC__seekable_stream_decoder_set_metadata_callback(_decoder, &FLACStream::callWrapMetadata); + ::FLAC__seekable_stream_decoder_set_error_callback(_decoder, &FLACStream::callWrapError); + ::FLAC__seekable_stream_decoder_set_client_data(_decoder, (void*)this); + + success = (::FLAC__seekable_stream_decoder_init(_decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK); +#else + success = (::FLAC__stream_decoder_init_stream( + _decoder, + &FLACStream::callWrapRead, + &FLACStream::callWrapSeek, + &FLACStream::callWrapTell, + &FLACStream::callWrapLength, + &FLACStream::callWrapEOF, + &FLACStream::callWrapWrite, + &FLACStream::callWrapMetadata, + &FLACStream::callWrapError, + (void*)this + ) == FLAC__STREAM_DECODER_INIT_STATUS_OK); +#endif + if (success) { + if (processUntilEndOfMetadata() && _streaminfo.channels > 0) { + _lastSample = _streaminfo.total_samples + 1; + _length = Timestamp(0, _lastSample - 1, getRate()); + return; // no error occurred + } + } + + warning("FLACStream: could not create audio stream"); +} + +FLACStream::~FLACStream() { + if (_decoder != NULL) { +#ifdef LEGACY_FLAC + (void) ::FLAC__seekable_stream_decoder_finish(_decoder); + ::FLAC__seekable_stream_decoder_delete(_decoder); +#else + (void) ::FLAC__stream_decoder_finish(_decoder); + ::FLAC__stream_decoder_delete(_decoder); +#endif + } + if (_disposeAfterUse) + delete _inStream; +} + +inline FLAC__StreamDecoderState FLACStream::getStreamDecoderState() const { + assert(_decoder != NULL); +#ifdef LEGACY_FLAC + return ::FLAC__seekable_stream_decoder_get_stream_decoder_state(_decoder); +#else + return ::FLAC__stream_decoder_get_state(_decoder); +#endif +} + +inline bool FLACStream::processSingleBlock() { + assert(_decoder != NULL); +#ifdef LEGACY_FLAC + return 0 != ::FLAC__seekable_stream_decoder_process_single(_decoder); +#else + return 0 != ::FLAC__stream_decoder_process_single(_decoder); +#endif +} + +inline bool FLACStream::processUntilEndOfMetadata() { + assert(_decoder != NULL); +#ifdef LEGACY_FLAC + return 0 != ::FLAC__seekable_stream_decoder_process_until_end_of_metadata(_decoder); +#else + return 0 != ::FLAC__stream_decoder_process_until_end_of_metadata(_decoder); +#endif +} + +bool FLACStream::seekAbsolute(FLAC__uint64 sample) { + assert(_decoder != NULL); +#ifdef LEGACY_FLAC + const bool result = (0 != ::FLAC__seekable_stream_decoder_seek_absolute(_decoder, sample)); +#else + const bool result = (0 != ::FLAC__stream_decoder_seek_absolute(_decoder, sample)); +#endif + if (result) { + _lastSampleWritten = (_lastSample != 0 && sample >= _lastSample); // only set if we are SURE + } + return result; +} + +bool FLACStream::seek(const Timestamp &where) { + _sampleCache.bufFill = 0; + _sampleCache.bufReadPos = NULL; + // FLAC uses the sample pair number, thus we always use "false" for the isStereo parameter + // of the convertTimeToStreamPos helper. + return seekAbsolute((FLAC__uint64)convertTimeToStreamPos(where, getRate(), false).totalNumberOfFrames()); +} + +int FLACStream::readBuffer(int16 *buffer, const int numSamples) { + const uint numChannels = getChannels(); + + if (numChannels == 0) { + warning("FLACStream: Stream not successfully initialised, cant playback"); + return -1; // streaminfo wasnt read! + } + + assert(numSamples % numChannels == 0); // must be multiple of channels! + assert(buffer != NULL); + assert(_outBuffer == NULL); + assert(_requestedSamples == 0); + + _outBuffer = buffer; + _requestedSamples = numSamples; + + // If there is still data in our buffer from the last time around, + // copy that first. + if (_sampleCache.bufFill > 0) { + assert(_sampleCache.bufReadPos >= _sampleCache.bufData); + assert(_sampleCache.bufFill % numChannels == 0); + + const uint copySamples = MIN((uint)numSamples, _sampleCache.bufFill); + memcpy(buffer, _sampleCache.bufReadPos, copySamples*sizeof(buffer[0])); + + _outBuffer = buffer + copySamples; + _requestedSamples = numSamples - copySamples; + _sampleCache.bufReadPos += copySamples; + _sampleCache.bufFill -= copySamples; + } + + bool decoderOk = true; + + FLAC__StreamDecoderState state = getStreamDecoderState(); + + // Keep poking FLAC to process more samples until we completely satisfied the request + // respectively until we run out of data. + while (!_lastSampleWritten && _requestedSamples > 0 && state == FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC) { + assert(_sampleCache.bufFill == 0); + assert(_requestedSamples % numChannels == 0); + processSingleBlock(); + state = getStreamDecoderState(); + + if (state == FLAC__STREAM_DECODER_END_OF_STREAM) + _lastSampleWritten = true; + } + + // Error handling + switch (state) { + case FLAC__STREAM_DECODER_END_OF_STREAM: + _lastSampleWritten = true; + break; + case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: + break; + default: + decoderOk = false; + warning("FLACStream: An error occurred while decoding. DecoderState is: %s", + FLAC__StreamDecoderStateString[getStreamDecoderState()]); + } + + // Compute how many samples we actually produced + const int samples = (int)(_outBuffer - buffer); + assert(samples % numChannels == 0); + + _outBuffer = NULL; // basically unnecessary, only for the purpose of the asserts + _requestedSamples = 0; // basically unnecessary, only for the purpose of the asserts + + return decoderOk ? samples : -1; +} + +inline ::FLAC__SeekableStreamDecoderReadStatus FLACStream::callbackRead(FLAC__byte buffer[], FLAC_size_t *bytes) { + if (*bytes == 0) { +#ifdef LEGACY_FLAC + return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; /* abort to avoid a deadlock */ +#else + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; /* abort to avoid a deadlock */ +#endif + } + + const uint32 bytesRead = _inStream->read(buffer, *bytes); + + if (bytesRead == 0) { +#ifdef LEGACY_FLAC + return _inStream->eos() ? FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK : FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; +#else + return _inStream->eos() ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_ABORT; +#endif + } + + *bytes = static_cast(bytesRead); +#ifdef LEGACY_FLAC + return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; +#else + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +#endif +} + +void FLACStream::setBestConvertBufferMethod() { + PFCONVERTBUFFERS tempMethod = &FLACStream::convertBuffersGeneric; + + const uint numChannels = getChannels(); + const uint8 numBits = (uint8)_streaminfo.bits_per_sample; + + assert(numChannels >= 1); + assert(numBits >= 4 && numBits <=32); + + if (numChannels == 1) { + if (numBits == 8) + tempMethod = &FLACStream::convertBuffersMono8Bit; + if (numBits == BUFTYPE_BITS) + tempMethod = &FLACStream::convertBuffersMonoNS; + } else if (numChannels == 2) { + if (numBits == 8) + tempMethod = &FLACStream::convertBuffersStereo8Bit; + if (numBits == BUFTYPE_BITS) + tempMethod = &FLACStream::convertBuffersStereoNS; + } /* else ... */ + + _methodConvertBuffers = tempMethod; +} + +// 1 channel, no scaling +void FLACStream::convertBuffersMonoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { + assert(numChannels == 1); + assert(numBits == BUFTYPE_BITS); + + FLAC__int32 const* inChannel1 = inChannels[0]; + + while (numSamples >= 4) { + bufDestination[0] = static_cast(inChannel1[0]); + bufDestination[1] = static_cast(inChannel1[1]); + bufDestination[2] = static_cast(inChannel1[2]); + bufDestination[3] = static_cast(inChannel1[3]); + bufDestination += 4; + inChannel1 += 4; + numSamples -= 4; + } + + for (; numSamples > 0; --numSamples) { + *bufDestination++ = static_cast(*inChannel1++); + } + + inChannels[0] = inChannel1; + assert(numSamples == 0); // dint copy too many samples +} + +// 1 channel, scaling from 8Bit +void FLACStream::convertBuffersMono8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { + assert(numChannels == 1); + assert(numBits == 8); + assert(8 < BUFTYPE_BITS); + + FLAC__int32 const* inChannel1 = inChannels[0]; + + while (numSamples >= 4) { + bufDestination[0] = static_cast(inChannel1[0]) << (BUFTYPE_BITS - 8); + bufDestination[1] = static_cast(inChannel1[1]) << (BUFTYPE_BITS - 8); + bufDestination[2] = static_cast(inChannel1[2]) << (BUFTYPE_BITS - 8); + bufDestination[3] = static_cast(inChannel1[3]) << (BUFTYPE_BITS - 8); + bufDestination += 4; + inChannel1 += 4; + numSamples -= 4; + } + + for (; numSamples > 0; --numSamples) { + *bufDestination++ = static_cast(*inChannel1++) << (BUFTYPE_BITS - 8); + } + + inChannels[0] = inChannel1; + assert(numSamples == 0); // dint copy too many samples +} + +// 2 channels, no scaling +void FLACStream::convertBuffersStereoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { + assert(numChannels == 2); + assert(numBits == BUFTYPE_BITS); + assert(numSamples % 2 == 0); // must be integral multiply of channels + + + FLAC__int32 const* inChannel1 = inChannels[0]; // Left Channel + FLAC__int32 const* inChannel2 = inChannels[1]; // Right Channel + + while (numSamples >= 2*2) { + bufDestination[0] = static_cast(inChannel1[0]); + bufDestination[1] = static_cast(inChannel2[0]); + bufDestination[2] = static_cast(inChannel1[1]); + bufDestination[3] = static_cast(inChannel2[1]); + bufDestination += 2 * 2; + inChannel1 += 2; + inChannel2 += 2; + numSamples -= 2 * 2; + } + + while (numSamples > 0) { + bufDestination[0] = static_cast(*inChannel1++); + bufDestination[1] = static_cast(*inChannel2++); + bufDestination += 2; + numSamples -= 2; + } + + inChannels[0] = inChannel1; + inChannels[1] = inChannel2; + assert(numSamples == 0); // dint copy too many samples +} + +// 2 channels, scaling from 8Bit +void FLACStream::convertBuffersStereo8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { + assert(numChannels == 2); + assert(numBits == 8); + assert(numSamples % 2 == 0); // must be integral multiply of channels + assert(8 < BUFTYPE_BITS); + + FLAC__int32 const* inChannel1 = inChannels[0]; // Left Channel + FLAC__int32 const* inChannel2 = inChannels[1]; // Right Channel + + while (numSamples >= 2*2) { + bufDestination[0] = static_cast(inChannel1[0]) << (BUFTYPE_BITS - 8); + bufDestination[1] = static_cast(inChannel2[0]) << (BUFTYPE_BITS - 8); + bufDestination[2] = static_cast(inChannel1[1]) << (BUFTYPE_BITS - 8); + bufDestination[3] = static_cast(inChannel2[1]) << (BUFTYPE_BITS - 8); + bufDestination += 2 * 2; + inChannel1 += 2; + inChannel2 += 2; + numSamples -= 2 * 2; + } + + while (numSamples > 0) { + bufDestination[0] = static_cast(*inChannel1++) << (BUFTYPE_BITS - 8); + bufDestination[1] = static_cast(*inChannel2++) << (BUFTYPE_BITS - 8); + bufDestination += 2; + numSamples -= 2; + } + + inChannels[0] = inChannel1; + inChannels[1] = inChannel2; + assert(numSamples == 0); // dint copy too many samples +} + +// all Purpose-conversion - slowest of em all +void FLACStream::convertBuffersGeneric(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { + assert(numSamples % numChannels == 0); // must be integral multiply of channels + + if (numBits < BUFTYPE_BITS) { + const uint8 kPower = (uint8)(BUFTYPE_BITS - numBits); + + for (; numSamples > 0; numSamples -= numChannels) { + for (uint i = 0; i < numChannels; ++i) + *bufDestination++ = static_cast(*(inChannels[i]++)) << kPower; + } + } else if (numBits > BUFTYPE_BITS) { + const uint8 kPower = (uint8)(numBits - BUFTYPE_BITS); + + for (; numSamples > 0; numSamples -= numChannels) { + for (uint i = 0; i < numChannels; ++i) + *bufDestination++ = static_cast(*(inChannels[i]++) >> kPower); + } + } else { + for (; numSamples > 0; numSamples -= numChannels) { + for (uint i = 0; i < numChannels; ++i) + *bufDestination++ = static_cast(*(inChannels[i]++)); + } + } + + assert(numSamples == 0); // dint copy too many samples +} + +inline ::FLAC__StreamDecoderWriteStatus FLACStream::callbackWrite(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]) { + assert(frame->header.channels == _streaminfo.channels); + assert(frame->header.sample_rate == _streaminfo.sample_rate); + assert(frame->header.bits_per_sample == _streaminfo.bits_per_sample); + assert(frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER || _streaminfo.min_blocksize == _streaminfo.max_blocksize); + + // We require that either the sample cache is empty, or that no samples were requested + assert(_sampleCache.bufFill == 0 || _requestedSamples == 0); + + uint numSamples = frame->header.blocksize; + const uint numChannels = getChannels(); + const uint8 numBits = (uint8)_streaminfo.bits_per_sample; + + assert(_requestedSamples % numChannels == 0); // must be integral multiply of channels + + const FLAC__uint64 firstSampleNumber = (frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER) ? + frame->header.number.sample_number : (static_cast(frame->header.number.frame_number)) * _streaminfo.max_blocksize; + + // Check whether we are about to reach beyond the last sample we are supposed to play. + if (_lastSample != 0 && firstSampleNumber + numSamples >= _lastSample) { + numSamples = (uint)(firstSampleNumber >= _lastSample ? 0 : _lastSample - firstSampleNumber); + _lastSampleWritten = true; + } + + // The value in _requestedSamples counts raw samples, so if there are more than one + // channel, we have to multiply the number of available sample "pairs" by numChannels + numSamples *= numChannels; + + const FLAC__int32 *inChannels[MAX_OUTPUT_CHANNELS]; + for (uint i = 0; i < numChannels; ++i) + inChannels[i] = buffer[i]; + + // write the incoming samples directly into the buffer provided to us by the mixer + if (_requestedSamples > 0) { + assert(_requestedSamples % numChannels == 0); + assert(_outBuffer != NULL); + + // Copy & convert the available samples (limited both by how many we have available, and + // by how many are actually needed). + const uint copySamples = MIN(_requestedSamples, numSamples); + (*_methodConvertBuffers)(_outBuffer, inChannels, copySamples, numChannels, numBits); + + _requestedSamples -= copySamples; + numSamples -= copySamples; + _outBuffer += copySamples; + } + + // Write all remaining samples (i.e. those which didn't fit into the mixer buffer) + // into the sample cache. + if (_sampleCache.bufFill == 0) + _sampleCache.bufReadPos = _sampleCache.bufData; + const uint cacheSpace = (_sampleCache.bufData + BUFFER_SIZE) - (_sampleCache.bufReadPos + _sampleCache.bufFill); + assert(numSamples <= cacheSpace); + (*_methodConvertBuffers)(_sampleCache.bufReadPos + _sampleCache.bufFill, inChannels, numSamples, numChannels, numBits); + + _sampleCache.bufFill += numSamples; + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +inline ::FLAC__SeekableStreamDecoderSeekStatus FLACStream::callbackSeek(FLAC__uint64 absoluteByteOffset) { + _inStream->seek(absoluteByteOffset, SEEK_SET); + const bool result = (absoluteByteOffset == (FLAC__uint64)_inStream->pos()); + +#ifdef LEGACY_FLAC + return result ? FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK : FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR; +#else + return result ? FLAC__STREAM_DECODER_SEEK_STATUS_OK : FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; +#endif +} + +inline ::FLAC__SeekableStreamDecoderTellStatus FLACStream::callbackTell(FLAC__uint64 *absoluteByteOffset) { + *absoluteByteOffset = static_cast(_inStream->pos()); +#ifdef LEGACY_FLAC + return FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK; +#else + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +#endif +} + +inline ::FLAC__SeekableStreamDecoderLengthStatus FLACStream::callbackLength(FLAC__uint64 *streamLength) { + *streamLength = static_cast(_inStream->size()); +#ifdef LEGACY_FLAC + return FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK; +#else + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; +#endif +} + +inline bool FLACStream::callbackEOF() { + return _inStream->eos(); +} + + +inline void FLACStream::callbackMetadata(const ::FLAC__StreamMetadata *metadata) { + assert(_decoder != NULL); + assert(metadata->type == FLAC__METADATA_TYPE_STREAMINFO); // others arent really interesting + + _streaminfo = metadata->data.stream_info; + setBestConvertBufferMethod(); // should be set after getting stream-information. FLAC always parses the info first +} +inline void FLACStream::callbackError(::FLAC__StreamDecoderErrorStatus status) { + // some of these are non-critical-Errors + debug(1, "FLACStream: An error occurred while decoding. DecoderState is: %s", + FLAC__StreamDecoderErrorStatusString[status]); +} + +/* Static Callback Wrappers */ +::FLAC__SeekableStreamDecoderReadStatus FLACStream::callWrapRead(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], FLAC_size_t *bytes, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + return instance->callbackRead(buffer, bytes); +} + +::FLAC__SeekableStreamDecoderSeekStatus FLACStream::callWrapSeek(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absoluteByteOffset, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + return instance->callbackSeek(absoluteByteOffset); +} + +::FLAC__SeekableStreamDecoderTellStatus FLACStream::callWrapTell(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absoluteByteOffset, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + return instance->callbackTell(absoluteByteOffset); +} + +::FLAC__SeekableStreamDecoderLengthStatus FLACStream::callWrapLength(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *streamLength, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + return instance->callbackLength(streamLength); +} + +FLAC__bool FLACStream::callWrapEOF(const ::FLAC__SeekableStreamDecoder *decoder, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + return instance->callbackEOF(); +} + +::FLAC__StreamDecoderWriteStatus FLACStream::callWrapWrite(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + return instance->callbackWrite(frame, buffer); +} + +void FLACStream::callWrapMetadata(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__StreamMetadata *metadata, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + instance->callbackMetadata(metadata); +} + +void FLACStream::callWrapError(const ::FLAC__SeekableStreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + instance->callbackError(status); +} + + +#pragma mark - +#pragma mark --- FLAC factory functions --- +#pragma mark - + +SeekableAudioStream *makeFLACStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse) { + SeekableAudioStream *s = new FLACStream(stream, disposeAfterUse); + if (s && s->endOfData()) { + delete s; + return 0; + } else { + return s; + } +} + +} // End of namespace Audio + +#endif // #ifdef USE_FLAC diff --git a/audio/decoders/flac.h b/audio/decoders/flac.h new file mode 100644 index 0000000000..17f95ec1fb --- /dev/null +++ b/audio/decoders/flac.h @@ -0,0 +1,75 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - agos + * - draci + * - kyra + * - m4 + * - queen + * - saga + * - sci + * - scumm + * - sword1 + * - sword2 + * - touche + * - tucker + */ + +#ifndef SOUND_FLAC_H +#define SOUND_FLAC_H + +#include "common/scummsys.h" +#include "common/types.h" + +#ifdef USE_FLAC + +namespace Common { + class SeekableReadStream; +} + +namespace Audio { + +class AudioStream; +class SeekableAudioStream; + +/** + * Create a new SeekableAudioStream from the FLAC data in the given stream. + * Allows for seeking (which is why we require a SeekableReadStream). + * + * @param stream the SeekableReadStream from which to read the FLAC data + * @param disposeAfterUse whether to delete the stream after use + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *makeFLACStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif // #ifdef USE_FLAC +#endif // #ifndef SOUND_FLAC_H diff --git a/audio/decoders/iff_sound.cpp b/audio/decoders/iff_sound.cpp new file mode 100644 index 0000000000..2ec189c586 --- /dev/null +++ b/audio/decoders/iff_sound.cpp @@ -0,0 +1,130 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/decoders/iff_sound.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" +#include "common/iff_container.h" +#include "common/func.h" + +namespace Audio { + +struct Voice8Header { + uint32 oneShotHiSamples; + uint32 repeatHiSamples; + uint32 samplesPerHiCycle; + uint16 samplesPerSec; + byte octaves; + byte compression; + uint32 volume; + + Voice8Header() { + memset(this, 0, sizeof(Voice8Header)); + } + + void load(Common::ReadStream &stream); +}; + +void Voice8Header::load(Common::ReadStream &stream) { + oneShotHiSamples = stream.readUint32BE(); + repeatHiSamples = stream.readUint32BE(); + samplesPerHiCycle = stream.readUint32BE(); + samplesPerSec = stream.readUint16BE(); + octaves = stream.readByte(); + compression = stream.readByte(); + volume = stream.readUint32BE(); +} + + + +struct A8SVXLoader { + Voice8Header _header; + int8 *_data; + uint32 _dataSize; + + void load(Common::ReadStream &input) { + Common::IFFParser parser(&input); + Common::Functor1Mem< Common::IFFChunk&, bool, A8SVXLoader > c(this, &A8SVXLoader::callback); + parser.parse(c); + } + + bool callback(Common::IFFChunk &chunk) { + switch (chunk._type) { + case ID_VHDR: + _header.load(*chunk._stream); + break; + + case ID_BODY: + _dataSize = chunk._size; + _data = (int8*)malloc(_dataSize); + assert(_data); + loadData(chunk._stream); + return true; + } + + return false; + } + + void loadData(Common::ReadStream *stream) { + switch (_header.compression) { + case 0: + stream->read(_data, _dataSize); + break; + + case 1: + // implement other formats here + error("compressed IFF audio is not supported"); + break; + } + + } +}; + + +AudioStream *make8SVXStream(Common::ReadStream &input, bool loop) { + A8SVXLoader loader; + loader.load(input); + + SeekableAudioStream *stream = Audio::makeRawStream((byte *)loader._data, loader._dataSize, loader._header.samplesPerSec, 0); + + uint32 loopStart = 0, loopEnd = 0; + if (loop) { + // the standard way to loop 8SVX audio implies use of the oneShotHiSamples and + // repeatHiSamples fields + loopStart = 0; + loopEnd = loader._header.oneShotHiSamples + loader._header.repeatHiSamples; + + if (loopStart != loopEnd) { + return new SubLoopingAudioStream(stream, 0, + Timestamp(0, loopStart, loader._header.samplesPerSec), + Timestamp(0, loopEnd, loader._header.samplesPerSec)); + } + } + + return stream; +} + +} diff --git a/audio/decoders/iff_sound.h b/audio/decoders/iff_sound.h new file mode 100644 index 0000000000..4e53059380 --- /dev/null +++ b/audio/decoders/iff_sound.h @@ -0,0 +1,47 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - parallaction + */ + +#ifndef SOUND_IFF_H +#define SOUND_IFF_H + +namespace Common { + class ReadStream; +} + +namespace Audio { + +class AudioStream; + +AudioStream *make8SVXStream(Common::ReadStream &stream, bool loop); + +} + +#endif diff --git a/audio/decoders/mac_snd.cpp b/audio/decoders/mac_snd.cpp new file mode 100644 index 0000000000..7c1a2f75f0 --- /dev/null +++ b/audio/decoders/mac_snd.cpp @@ -0,0 +1,116 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * The code in this file is based on information found at + * http://developer.apple.com/legacy/mac/library/documentation/mac/Sound/Sound-60.html#HEADING60-15 + * + * We implement both type 1 and type 2 snd resources, but only those that are sampled + */ + +#include "common/util.h" +#include "common/stream.h" + +#include "audio/decoders/mac_snd.h" +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" + +namespace Audio { + +SeekableAudioStream *makeMacSndStream(Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse) { + + uint16 sndType = stream->readUint16BE(); + + if (sndType == 1) { + // "normal" snd resources + if (stream->readUint16BE() != 1) { + warning("makeMacSndStream(): Unsupported data type count"); + return 0; + } + + if (stream->readUint16BE() != 5) { + // 5 == sampled + warning("makeMacSndStream(): Unsupported data type"); + return 0; + } + + stream->readUint32BE(); // initialization option + } else if (sndType == 2) { + // old HyperCard snd resources + stream->readUint16BE(); // reference count (unused) + } else { + warning("makeMacSndStream(): Unknown format type %d", sndType); + return 0; + } + + // We really should never get this as long as we have sampled data only + if (stream->readUint16BE() != 1) { + warning("makeMacSndStream(): Unsupported command count"); + return 0; + } + + uint16 command = stream->readUint16BE(); + + // 0x8050 - soundCmd (with dataOffsetFlag set): install a sampled sound as a voice + // 0x8051 - bufferCmd (with dataOffsetFlag set): play a sample sound + if (command != 0x8050 && command != 0x8051) { + warning("makeMacSndStream(): Unsupported command %04x", command); + return 0; + } + + stream->readUint16BE(); // 0 + uint32 soundHeaderOffset = stream->readUint32BE(); + + stream->seek(soundHeaderOffset); + + uint32 soundDataOffset = stream->readUint32BE(); + uint32 size = stream->readUint32BE(); + uint16 rate = stream->readUint32BE() >> 16; // Really fixed point, but we only support integer rates + stream->readUint32BE(); // loop start + stream->readUint32BE(); // loop end + byte encoding = stream->readByte(); + stream->readByte(); // base frequency + + if (encoding != 0) { + // 0 == PCM + warning("makeMacSndStream(): Unsupported compression %d", encoding); + return 0; + } + + stream->skip(soundDataOffset); + + byte *data = (byte *)malloc(size); + assert(data); + stream->read(data, size); + + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + + // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. + return makeRawStream(data, size, rate, Audio::FLAG_UNSIGNED); +} + +} // End of namespace Audio diff --git a/audio/decoders/mac_snd.h b/audio/decoders/mac_snd.h new file mode 100644 index 0000000000..198a61333e --- /dev/null +++ b/audio/decoders/mac_snd.h @@ -0,0 +1,58 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - sci + */ + +#ifndef SOUND_MAC_SND_H +#define SOUND_MAC_SND_H + +#include "common/scummsys.h" +#include "common/types.h" + +namespace Common { class SeekableReadStream; } + +namespace Audio { + +class SeekableAudioStream; + +/** + * Try to load a Mac snd resource from the given seekable stream and create a SeekableAudioStream + * from that data. + * + * @param stream the SeekableReadStream from which to read the snd data + * @param disposeAfterUse whether to delete the stream after use + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *makeMacSndStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/mp3.cpp b/audio/decoders/mp3.cpp new file mode 100644 index 0000000000..53d68fa9db --- /dev/null +++ b/audio/decoders/mp3.cpp @@ -0,0 +1,375 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/decoders/mp3.h" + +#ifdef USE_MAD + +#include "common/debug.h" +#include "common/stream.h" +#include "common/util.h" + +#include "audio/audiostream.h" + +#include + +#if defined(__PSP__) + #include "backends/platform/psp/mp3.h" +#endif +namespace Audio { + + +#pragma mark - +#pragma mark --- MP3 (MAD) stream --- +#pragma mark - + + +class MP3Stream : public SeekableAudioStream { +protected: + enum State { + MP3_STATE_INIT, // Need to init the decoder + MP3_STATE_READY, // ready for processing data + MP3_STATE_EOS // end of data reached (may need to loop) + }; + + Common::SeekableReadStream *_inStream; + DisposeAfterUse::Flag _disposeAfterUse; + + uint _posInFrame; + State _state; + + Timestamp _length; + mad_timer_t _totalTime; + + mad_stream _stream; + mad_frame _frame; + mad_synth _synth; + + enum { + BUFFER_SIZE = 5 * 8192 + }; + + // This buffer contains a slab of input data + byte _buf[BUFFER_SIZE + MAD_BUFFER_GUARD]; + +public: + MP3Stream(Common::SeekableReadStream *inStream, + DisposeAfterUse::Flag dispose); + ~MP3Stream(); + + int readBuffer(int16 *buffer, const int numSamples); + + bool endOfData() const { return _state == MP3_STATE_EOS; } + bool isStereo() const { return MAD_NCHANNELS(&_frame.header) == 2; } + int getRate() const { return _frame.header.samplerate; } + + bool seek(const Timestamp &where); + Timestamp getLength() const { return _length; } +protected: + void decodeMP3Data(); + void readMP3Data(); + + void initStream(); + void readHeader(); + void deinitStream(); +}; + +MP3Stream::MP3Stream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) : + _inStream(inStream), + _disposeAfterUse(dispose), + _posInFrame(0), + _state(MP3_STATE_INIT), + _length(0, 1000), + _totalTime(mad_timer_zero) { + + // The MAD_BUFFER_GUARD must always contain zeros (the reason + // for this is that the Layer III Huffman decoder of libMAD + // may read a few bytes beyond the end of the input buffer). + memset(_buf + BUFFER_SIZE, 0, MAD_BUFFER_GUARD); + + // Calculate the length of the stream + initStream(); + + while (_state != MP3_STATE_EOS) + readHeader(); + + // To rule out any invalid sample rate to be encountered here, say in case the + // MP3 stream is invalid, we just check the MAD error code here. + // We need to assure this, since else we might trigger an assertion in Timestamp + // (When getRate() returns 0 or a negative number to be precise). + // Note that we allow "MAD_ERROR_BUFLEN" as error code here, since according + // to mad.h it is also set on EOF. + if ((_stream.error == MAD_ERROR_NONE || _stream.error == MAD_ERROR_BUFLEN) && getRate() > 0) + _length = Timestamp(mad_timer_count(_totalTime, MAD_UNITS_MILLISECONDS), getRate()); + + deinitStream(); + + // Reinit stream + _state = MP3_STATE_INIT; + + // Decode the first chunk of data. This is necessary so that _frame + // is setup and isStereo() and getRate() return correct results. + decodeMP3Data(); +} + +MP3Stream::~MP3Stream() { + deinitStream(); + + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _inStream; +} + +void MP3Stream::decodeMP3Data() { + do { + if (_state == MP3_STATE_INIT) + initStream(); + + if (_state == MP3_STATE_EOS) + return; + + // If necessary, load more data into the stream decoder + if (_stream.error == MAD_ERROR_BUFLEN) + readMP3Data(); + + while (_state == MP3_STATE_READY) { + _stream.error = MAD_ERROR_NONE; + + // Decode the next frame + if (mad_frame_decode(&_frame, &_stream) == -1) { + if (_stream.error == MAD_ERROR_BUFLEN) { + break; // Read more data + } else if (MAD_RECOVERABLE(_stream.error)) { + // Note: we will occasionally see MAD_ERROR_BADDATAPTR errors here. + // These are normal and expected (caused by our frame skipping (i.e. "seeking") + // code above). + debug(6, "MP3Stream: Recoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream)); + continue; + } else { + warning("MP3Stream: Unrecoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream)); + break; + } + } + + // Synthesize PCM data + mad_synth_frame(&_synth, &_frame); + _posInFrame = 0; + break; + } + } while (_state != MP3_STATE_EOS && _stream.error == MAD_ERROR_BUFLEN); + + if (_stream.error != MAD_ERROR_NONE) + _state = MP3_STATE_EOS; +} + +void MP3Stream::readMP3Data() { + uint32 remaining = 0; + + // Give up immediately if we already used up all data in the stream + if (_inStream->eos()) { + _state = MP3_STATE_EOS; + return; + } + + if (_stream.next_frame) { + // If there is still data in the MAD stream, we need to preserve it. + // Note that we use memmove, as we are reusing the same buffer, + // and hence the data regions we copy from and to may overlap. + remaining = _stream.bufend - _stream.next_frame; + assert(remaining < BUFFER_SIZE); // Paranoia check + memmove(_buf, _stream.next_frame, remaining); + } + + // Try to read the next block + uint32 size = _inStream->read(_buf + remaining, BUFFER_SIZE - remaining); + if (size <= 0) { + _state = MP3_STATE_EOS; + return; + } + + // Feed the data we just read into the stream decoder + _stream.error = MAD_ERROR_NONE; + mad_stream_buffer(&_stream, _buf, size + remaining); +} + +bool MP3Stream::seek(const Timestamp &where) { + if (where == _length) { + _state = MP3_STATE_EOS; + return true; + } else if (where > _length) { + return false; + } + + const uint32 time = where.msecs(); + + mad_timer_t destination; + mad_timer_set(&destination, time / 1000, time % 1000, 1000); + + if (_state != MP3_STATE_READY || mad_timer_compare(destination, _totalTime) < 0) + initStream(); + + while (mad_timer_compare(destination, _totalTime) > 0 && _state != MP3_STATE_EOS) + readHeader(); + + decodeMP3Data(); + + return (_state != MP3_STATE_EOS); +} + +void MP3Stream::initStream() { + if (_state != MP3_STATE_INIT) + deinitStream(); + + // Init MAD + mad_stream_init(&_stream); + mad_frame_init(&_frame); + mad_synth_init(&_synth); + + // Reset the stream data + _inStream->seek(0, SEEK_SET); + _totalTime = mad_timer_zero; + _posInFrame = 0; + + // Update state + _state = MP3_STATE_READY; + + // Read the first few sample bytes + readMP3Data(); +} + +void MP3Stream::readHeader() { + if (_state != MP3_STATE_READY) + return; + + // If necessary, load more data into the stream decoder + if (_stream.error == MAD_ERROR_BUFLEN) + readMP3Data(); + + while (_state != MP3_STATE_EOS) { + _stream.error = MAD_ERROR_NONE; + + // Decode the next header. Note: mad_frame_decode would do this for us, too. + // However, for seeking we don't want to decode the full frame (else it would + // be far too slow). Hence we perform this explicitly in a separate step. + if (mad_header_decode(&_frame.header, &_stream) == -1) { + if (_stream.error == MAD_ERROR_BUFLEN) { + readMP3Data(); // Read more data + continue; + } else if (MAD_RECOVERABLE(_stream.error)) { + debug(6, "MP3Stream: Recoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream)); + continue; + } else { + warning("MP3Stream: Unrecoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream)); + break; + } + } + + // Sum up the total playback time so far + mad_timer_add(&_totalTime, _frame.header.duration); + break; + } + + if (_stream.error != MAD_ERROR_NONE) + _state = MP3_STATE_EOS; +} + +void MP3Stream::deinitStream() { + if (_state == MP3_STATE_INIT) + return; + + // Deinit MAD + mad_synth_finish(&_synth); + mad_frame_finish(&_frame); + mad_stream_finish(&_stream); + + _state = MP3_STATE_EOS; +} + +static inline int scale_sample(mad_fixed_t sample) { + // round + sample += (1L << (MAD_F_FRACBITS - 16)); + + // clip + if (sample > MAD_F_ONE - 1) + sample = MAD_F_ONE - 1; + else if (sample < -MAD_F_ONE) + sample = -MAD_F_ONE; + + // quantize and scale to not saturate when mixing a lot of channels + return sample >> (MAD_F_FRACBITS + 1 - 16); +} + +int MP3Stream::readBuffer(int16 *buffer, const int numSamples) { + int samples = 0; + // Keep going as long as we have input available + while (samples < numSamples && _state != MP3_STATE_EOS) { + const int len = MIN(numSamples, samples + (int)(_synth.pcm.length - _posInFrame) * MAD_NCHANNELS(&_frame.header)); + while (samples < len) { + *buffer++ = (int16)scale_sample(_synth.pcm.samples[0][_posInFrame]); + samples++; + if (MAD_NCHANNELS(&_frame.header) == 2) { + *buffer++ = (int16)scale_sample(_synth.pcm.samples[1][_posInFrame]); + samples++; + } + _posInFrame++; + } + if (_posInFrame >= _synth.pcm.length) { + // We used up all PCM data in the current frame -- read & decode more + decodeMP3Data(); + } + } + return samples; +} + + +#pragma mark - +#pragma mark --- MP3 factory functions --- +#pragma mark - + +SeekableAudioStream *makeMP3Stream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse) { + +#if defined(__PSP__) + SeekableAudioStream *s = 0; + + if (Mp3PspStream::isOkToCreateStream()) + s = new Mp3PspStream(stream, disposeAfterUse); + + if (!s) // go to regular MAD mp3 stream if ME fails + s = new MP3Stream(stream, disposeAfterUse); +#else + SeekableAudioStream *s = new MP3Stream(stream, disposeAfterUse); +#endif + if (s && s->endOfData()) { + delete s; + return 0; + } else { + return s; + } +} + +} // End of namespace Audio + +#endif // #ifdef USE_MAD diff --git a/audio/decoders/mp3.h b/audio/decoders/mp3.h new file mode 100644 index 0000000000..72bc6e1b3e --- /dev/null +++ b/audio/decoders/mp3.h @@ -0,0 +1,76 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - agos + * - draci + * - kyra + * - m4 + * - mohawk + * - queen + * - saga + * - sci + * - scumm + * - sword1 + * - sword2 + * - touche + * - tucker + */ + +#ifndef SOUND_MP3_H +#define SOUND_MP3_H + +#include "common/scummsys.h" +#include "common/types.h" + +#ifdef USE_MAD + +namespace Common { + class SeekableReadStream; +} + +namespace Audio { + +class AudioStream; +class SeekableAudioStream; + +/** + * Create a new SeekableAudioStream from the MP3 data in the given stream. + * Allows for seeking (which is why we require a SeekableReadStream). + * + * @param stream the SeekableReadStream from which to read the MP3 data + * @param disposeAfterUse whether to delete the stream after use + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *makeMP3Stream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif // #ifdef USE_MAD +#endif // #ifndef SOUND_MP3_H diff --git a/audio/decoders/raw.cpp b/audio/decoders/raw.cpp new file mode 100644 index 0000000000..8b833c7838 --- /dev/null +++ b/audio/decoders/raw.cpp @@ -0,0 +1,356 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/endian.h" +#include "common/memstream.h" + +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" + +namespace Audio { + +// This used to be an inline template function, but +// buggy template function handling in MSVC6 forced +// us to go with the macro approach. So far this is +// the only template function that MSVC6 seemed to +// compile incorrectly. Knock on wood. +#define READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, ptr, isLE) \ + ((is16Bit ? (isLE ? READ_LE_UINT16(ptr) : READ_BE_UINT16(ptr)) : (*ptr << 8)) ^ (isUnsigned ? 0x8000 : 0)) + + +#pragma mark - +#pragma mark --- RawStream --- +#pragma mark - + +/** + * This is a stream, which allows for playing raw PCM data from a stream. + * It also features playback of multiple blocks from a given stream. + */ +template +class RawStream : public SeekableAudioStream { +public: + RawStream(int rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream, const RawStreamBlockList &blocks) + : _rate(rate), _isStereo(stereo), _playtime(0, rate), _stream(stream), _disposeAfterUse(disposeStream), _blocks(blocks), _curBlock(_blocks.begin()), _blockLeft(0), _buffer(0) { + + assert(_blocks.size() > 0); + + // Setup our buffer for readBuffer + _buffer = new byte[kSampleBufferLength * (is16Bit ? 2 : 1)]; + assert(_buffer); + + // Set current buffer state, playing first block + _stream->seek(_curBlock->pos, SEEK_SET); + + // In case of an error we will stop (or rather + // not start) stream playback. + if (_stream->err()) { + _blockLeft = 0; + _curBlock = _blocks.end(); + } else { + _blockLeft = _curBlock->len; + } + + // Add up length of all blocks in order to caluclate total play time + int32 len = 0; + for (RawStreamBlockList::const_iterator i = _blocks.begin(); i != _blocks.end(); ++i) { + assert(i->len % (_isStereo ? 2 : 1) == 0); + len += i->len; + } + + _playtime = Timestamp(0, len / (_isStereo ? 2 : 1), rate); + } + + ~RawStream() { + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _stream; + + delete[] _buffer; + } + + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { return _isStereo; } + bool endOfData() const { return (_curBlock == _blocks.end()) && (_blockLeft == 0); } + + int getRate() const { return _rate; } + Timestamp getLength() const { return _playtime; } + + bool seek(const Timestamp &where); +private: + const int _rate; ///< Sample rate of stream + const bool _isStereo; ///< Whether this is an stereo stream + Timestamp _playtime; ///< Calculated total play time + Common::SeekableReadStream *_stream; ///< Stream to read data from + const DisposeAfterUse::Flag _disposeAfterUse; ///< Indicates whether the stream object should be deleted when this RawStream is destructed + const RawStreamBlockList _blocks; ///< Audio block list + + RawStreamBlockList::const_iterator _curBlock; ///< Current audio block number + int32 _blockLeft; ///< How many bytes are still left in the current block + + /** + * Advance one block in the stream in case + * the current one is empty. + */ + void updateBlockIfNeeded(); + + byte *_buffer; ///< Buffer used in readBuffer + enum { + /** + * How many samples we can buffer at once. + * + * TODO: Check whether this size suffices + * for systems with slow disk I/O. + */ + kSampleBufferLength = 2048 + }; + + /** + * Fill the temporary sample buffer used in readBuffer. + * + * @param maxSamples Maximum samples to read. + * @return actual count of samples read. + */ + int fillBuffer(int maxSamples); +}; + +template +int RawStream::readBuffer(int16 *buffer, const int numSamples) { + int samplesLeft = numSamples; + + while (samplesLeft > 0) { + // Try to read up to "samplesLeft" samples. + int len = fillBuffer(samplesLeft); + + // In case we were not able to read any samples + // we will stop reading here. + if (!len) + break; + + // Adjust the samples left to read. + samplesLeft -= len; + + // Copy the data to the caller's buffer. + const byte *src = _buffer; + while (len-- > 0) { + *buffer++ = READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, src, isLE); + src += (is16Bit ? 2 : 1); + } + } + + return numSamples - samplesLeft; +} + +template +int RawStream::fillBuffer(int maxSamples) { + int bufferedSamples = 0; + byte *dst = _buffer; + + // We can only read up to "kSampleBufferLength" samples + // so we take this into consideration, when trying to + // read up to maxSamples. + maxSamples = MIN(kSampleBufferLength, maxSamples); + + // We will only read up to maxSamples + while (maxSamples > 0 && !endOfData()) { + // Calculate how many samples we can safely read + // from the current block. + const int len = MIN(maxSamples, _blockLeft); + + // Try to read all the sample data and update the + // destination pointer. + const int bytesRead = _stream->read(dst, len * (is16Bit ? 2 : 1)); + dst += bytesRead; + + // Calculate how many samples we actually read. + const int samplesRead = bytesRead / (is16Bit ? 2 : 1); + + // Update all status variables + bufferedSamples += samplesRead; + maxSamples -= samplesRead; + _blockLeft -= samplesRead; + + // In case of an error we will stop + // stream playback. + if (_stream->err()) { + _blockLeft = 0; + _curBlock = _blocks.end(); + } + + // Advance to the next block in case the current + // one is already finished. + updateBlockIfNeeded(); + } + + return bufferedSamples; +} + +template +void RawStream::updateBlockIfNeeded() { + // Have we now finished this block? If so, read the next block + if (_blockLeft == 0 && _curBlock != _blocks.end()) { + // Next block + ++_curBlock; + + // Check whether we reached the end of the stream + // yet. In case we did not do this, we will just + // setup the next block as new block. + if (_curBlock != _blocks.end()) { + _stream->seek(_curBlock->pos, SEEK_SET); + + // In case of an error we will stop + // stream playback. + if (_stream->err()) { + _blockLeft = 0; + _curBlock = _blocks.end(); + } else { + _blockLeft = _curBlock->len; + } + } + } +} + +template +bool RawStream::seek(const Timestamp &where) { + _blockLeft = 0; + _curBlock = _blocks.end(); + + if (where > _playtime) + return false; + + const uint32 seekSample = convertTimeToStreamPos(where, getRate(), isStereo()).totalNumberOfFrames(); + uint32 curSample = 0; + + // Search for the disk block in which the specific sample is placed + for (_curBlock = _blocks.begin(); _curBlock != _blocks.end(); ++_curBlock) { + uint32 nextBlockSample = curSample + _curBlock->len; + + if (nextBlockSample > seekSample) + break; + + curSample = nextBlockSample; + } + + if (_curBlock == _blocks.end()) { + return ((seekSample - curSample) == 0); + } else { + const uint32 offset = seekSample - curSample; + + _stream->seek(_curBlock->pos + offset * (is16Bit ? 2 : 1), SEEK_SET); + + // In case of an error we will stop + // stream playback. + if (_stream->err()) { + _blockLeft = 0; + _curBlock = _blocks.end(); + } else { + _blockLeft = _curBlock->len - offset; + } + + return true; + } +} + +#pragma mark - +#pragma mark --- Raw stream factories --- +#pragma mark - + +/* In the following, we use preprocessor / macro tricks to simplify the code + * which instantiates the input streams. We used to use template functions for + * this, but MSVC6 / EVC 3-4 (used for WinCE builds) are extremely buggy when it + * comes to this feature of C++... so as a compromise we use macros to cut down + * on the (source) code duplication a bit. + * So while normally macro tricks are said to make maintenance harder, in this + * particular case it should actually help it :-) + */ + +#define MAKE_RAW_STREAM(UNSIGNED) \ + if (is16Bit) { \ + if (isLE) \ + return new RawStream(rate, isStereo, disposeAfterUse, stream, blockList); \ + else \ + return new RawStream(rate, isStereo, disposeAfterUse, stream, blockList); \ + } else \ + return new RawStream(rate, isStereo, disposeAfterUse, stream, blockList) + +SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream, + const RawStreamBlockList &blockList, + int rate, + byte flags, + DisposeAfterUse::Flag disposeAfterUse) { + const bool isStereo = (flags & Audio::FLAG_STEREO) != 0; + const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0; + const bool isUnsigned = (flags & Audio::FLAG_UNSIGNED) != 0; + const bool isLE = (flags & Audio::FLAG_LITTLE_ENDIAN) != 0; + + if (blockList.empty()) { + warning("Empty block list passed to makeRawStream"); + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + return 0; + } + + if (isUnsigned) { + MAKE_RAW_STREAM(true); + } else { + MAKE_RAW_STREAM(false); + } +} + +SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream, + int rate, byte flags, + DisposeAfterUse::Flag disposeAfterUse) { + RawStreamBlockList blocks; + RawStreamBlock block; + block.pos = 0; + + const bool isStereo = (flags & Audio::FLAG_STEREO) != 0; + const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0; + + assert(stream->size() % ((is16Bit ? 2 : 1) * (isStereo ? 2 : 1)) == 0); + + block.len = stream->size() / (is16Bit ? 2 : 1); + blocks.push_back(block); + + return makeRawStream(stream, blocks, rate, flags, disposeAfterUse); +} + +SeekableAudioStream *makeRawStream(const byte *buffer, uint32 size, + int rate, byte flags, + DisposeAfterUse::Flag disposeAfterUse) { + return makeRawStream(new Common::MemoryReadStream(buffer, size, disposeAfterUse), rate, flags, DisposeAfterUse::YES); +} + +SeekableAudioStream *makeRawDiskStream_OLD(Common::SeekableReadStream *stream, RawStreamBlock *block, int numBlocks, + int rate, byte flags, DisposeAfterUse::Flag disposeStream) { + assert(numBlocks > 0); + RawStreamBlockList blocks; + for (int i = 0; i < numBlocks; ++i) + blocks.push_back(block[i]); + + return makeRawStream(stream, blocks, rate, flags, disposeStream); +} + +} // End of namespace Audio diff --git a/audio/decoders/raw.h b/audio/decoders/raw.h new file mode 100644 index 0000000000..3e9426012c --- /dev/null +++ b/audio/decoders/raw.h @@ -0,0 +1,153 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SOUND_RAW_H +#define SOUND_RAW_H + +#include "common/scummsys.h" +#include "common/types.h" + +#include "common/list.h" + + +namespace Common { class SeekableReadStream; } + + +namespace Audio { + +class AudioStream; +class SeekableAudioStream; + +/** + * Various flags which can be bit-ORed and then passed to + * makeRawStream and some other AudioStream factories + * to control their behavior. + * + * Engine authors are advised not to rely on a certain value or + * order of these flags (in particular, do not store them verbatim + * in savestates). + */ +enum RawFlags { + /** unsigned samples (default: signed) */ + FLAG_UNSIGNED = 1 << 0, + + /** sound is 16 bits wide (default: 8bit) */ + FLAG_16BITS = 1 << 1, + + /** samples are little endian (default: big endian) */ + FLAG_LITTLE_ENDIAN = 1 << 2, + + /** sound is in stereo (default: mono) */ + FLAG_STEREO = 1 << 3 +}; + + +/** + * Struct used to define the audio data to be played by a RawStream. + */ +struct RawStreamBlock { + int32 pos; ///< Position in stream of the block (in bytes of course!) + int32 len; ///< Length of the block (in raw samples, not sample pairs!) +}; + +/** + * List containing all blocks of a raw stream. + * @see RawStreamBlock + */ +typedef Common::List RawStreamBlockList; + +/** + * Creates an audio stream, which plays from the given buffer. + * + * @param buffer Buffer to play from. + * @param size Size of the buffer in bytes. + * @param rate Rate of the sound data. + * @param flags Audio flags combination. + * @see RawFlags + * @param disposeAfterUse Whether to free the buffer after use (with free!). + * @return The new SeekableAudioStream (or 0 on failure). + */ +SeekableAudioStream *makeRawStream(const byte *buffer, uint32 size, + int rate, byte flags, + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + +/** + * Creates an audio stream, which plays from the given stream. + * + * @param stream Stream object to play from. + * @param rate Rate of the sound data. + * @param flags Audio flags combination. + * @see RawFlags + * @param disposeAfterUse Whether to delete the stream after use. + * @return The new SeekableAudioStream (or 0 on failure). + */ +SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream, + int rate, byte flags, + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + +/** + * Creates an audio stream, which plays from the given stream. + * + * @param stream Stream object to play from. + * @param blockList List of blocks to play. + * @see RawDiskStreamAudioBlock + * @see RawStreamBlockList + * @param rate Rate of the sound data. + * @param flags Audio flags combination. + * @see RawFlags + * @param disposeAfterUse Whether to delete the stream after use. + * @return The new SeekableAudioStream (or 0 on failure). + */ +SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream, + const RawStreamBlockList &blockList, + int rate, + byte flags, + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + +/** + * NOTE: + * This API is considered deprecated. + * + * Creates a audio stream, which plays from given stream. + * + * @param stream Stream to play from + * @param block Pointer to an RawStreamBlock array + * @see RawStreamBlock + * @param numBlocks Number of blocks. + * @param rate The rate + * @param flags Flags combination. + * @see RawFlags + * @param disposeStream Whether the "stream" object should be destroyed after playback. + * @return The new SeekableAudioStream (or 0 on failure). + */ +SeekableAudioStream *makeRawDiskStream_OLD(Common::SeekableReadStream *stream, + RawStreamBlock *block, int numBlocks, + int rate, byte flags, + DisposeAfterUse::Flag disposeStream); + + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/vag.cpp b/audio/decoders/vag.cpp new file mode 100644 index 0000000000..2c3a36202a --- /dev/null +++ b/audio/decoders/vag.cpp @@ -0,0 +1,150 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/decoders/vag.h" +#include "audio/audiostream.h" +#include "common/stream.h" + +namespace Audio { + +class VagStream : public Audio::RewindableAudioStream { +public: + VagStream(Common::SeekableReadStream *stream, int rate); + ~VagStream(); + + bool isStereo() const { return false; } + bool endOfData() const { return _stream->pos() == _stream->size(); } + int getRate() const { return _rate; } + int readBuffer(int16 *buffer, const int numSamples); + + bool rewind(); +private: + Common::SeekableReadStream *_stream; + + byte _predictor; + double _samples[28]; + byte _samplesRemaining; + int _rate; + double _s1, _s2; +}; + +VagStream::VagStream(Common::SeekableReadStream *stream, int rate) : _stream(stream) { + _samplesRemaining = 0; + _predictor = 0; + _s1 = _s2 = 0.0; + _rate = rate; +} + + +VagStream::~VagStream() { + delete _stream; +} + +static const double s_vagDataTable[5][2] = + { + { 0.0, 0.0 }, + { 60.0 / 64.0, 0.0 }, + { 115.0 / 64.0, -52.0 / 64.0 }, + { 98.0 / 64.0, -55.0 / 64.0 }, + { 122.0 / 64.0, -60.0 / 64.0 } + }; + +int VagStream::readBuffer(int16 *buffer, const int numSamples) { + int32 samplesDecoded = 0; + + if (_samplesRemaining) { + byte i = 0; + + for (i = 28 - _samplesRemaining; i < 28 && samplesDecoded < numSamples; i++) { + _samples[i] = _samples[i] + _s1 * s_vagDataTable[_predictor][0] + _s2 * s_vagDataTable[_predictor][1]; + _s2 = _s1; + _s1 = _samples[i]; + int16 d = (int) (_samples[i] + 0.5); + buffer[samplesDecoded] = d; + samplesDecoded++; + } + +#if 0 + assert(i == 28); // We're screwed if this fails :P +#endif + // This might mean the file is corrupted, or that the stream has + // been closed. + if (i != 28) return 0; + + _samplesRemaining = 0; + } + + while (samplesDecoded < numSamples) { + byte i = 0; + + _predictor = _stream->readByte(); + byte shift = _predictor & 0xf; + _predictor >>= 4; + + if (_stream->readByte() == 7) + return samplesDecoded; + + for (i = 0; i < 28; i += 2) { + byte d = _stream->readByte(); + int16 s = (d & 0xf) << 12; + if (s & 0x8000) + s |= 0xffff0000; + _samples[i] = (double)(s >> shift); + s = (d & 0xf0) << 8; + if (s & 0x8000) + s |= 0xffff0000; + _samples[i + 1] = (double)(s >> shift); + } + + for (i = 0; i < 28 && samplesDecoded < numSamples; i++) { + _samples[i] = _samples[i] + _s1 * s_vagDataTable[_predictor][0] + _s2 * s_vagDataTable[_predictor][1]; + _s2 = _s1; + _s1 = _samples[i]; + int16 d = (int) (_samples[i] + 0.5); + buffer[samplesDecoded] = d; + samplesDecoded++; + } + + if (i != 27) + _samplesRemaining = 28 - i; + } + + return samplesDecoded; +} + +bool VagStream::rewind() { + _stream->seek(0); + _samplesRemaining = 0; + _predictor = 0; + _s1 = _s2 = 0.0; + + return true; +} + +RewindableAudioStream *makeVagStream(Common::SeekableReadStream *stream, int rate) { + return new VagStream(stream, rate); +} + +} diff --git a/audio/decoders/vag.h b/audio/decoders/vag.h new file mode 100644 index 0000000000..cdf91a8ea1 --- /dev/null +++ b/audio/decoders/vag.h @@ -0,0 +1,60 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - sword1 (PSX port of the game) + * - sword2 (PSX port of the game) + * - tinsel (PSX port of the game) + */ + +#ifndef SOUND_VAG_H +#define SOUND_VAG_H + +namespace Common { + class SeekableReadStream; +} + +namespace Audio { + +class AudioStream; +class RewindableAudioStream; + +/** + * Takes an input stream containing Vag sound data and creates + * an RewindableAudioStream from that. + * + * @param stream the SeekableReadStream from which to read the ADPCM data + * @param rate the sampling rate + * @return a new RewindableAudioStream, or NULL, if an error occurred + */ +RewindableAudioStream *makeVagStream( + Common::SeekableReadStream *stream, + int rate = 11025); + +} // End of namespace Sword1 + +#endif diff --git a/audio/decoders/voc.cpp b/audio/decoders/voc.cpp new file mode 100644 index 0000000000..b811a640ec --- /dev/null +++ b/audio/decoders/voc.cpp @@ -0,0 +1,403 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/debug.h" +#include "common/endian.h" +#include "common/util.h" +#include "common/stream.h" + +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/voc.h" + + +namespace Audio { + +int getSampleRateFromVOCRate(int vocSR) { + if (vocSR == 0xa5 || vocSR == 0xa6) { + return 11025; + } else if (vocSR == 0xd2 || vocSR == 0xd3) { + return 22050; + } else { + int sr = 1000000L / (256L - vocSR); + // inexact sampling rates occur e.g. in the kitchen in Monkey Island, + // very easy to reach right from the start of the game. + //warning("inexact sample rate used: %i (0x%x)", sr, vocSR); + return sr; + } +} + +static byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate, int &loops, int &begin_loop, int &end_loop) { + VocFileHeader fileHeader; + + debug(2, "loadVOCFromStream"); + + if (stream.read(&fileHeader, 8) != 8) + goto invalid; + + if (!memcmp(&fileHeader, "VTLK", 4)) { + if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader)) + goto invalid; + } else if (!memcmp(&fileHeader, "Creative", 8)) { + if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8) + goto invalid; + } else { + invalid:; + warning("loadVOCFromStream: Invalid header"); + return NULL; + } + + if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0) + error("loadVOCFromStream: Invalid header"); + if (fileHeader.desc[19] != 0x1A) + debug(3, "loadVOCFromStream: Partially invalid header"); + + int32 offset = FROM_LE_16(fileHeader.datablock_offset); + int16 version = FROM_LE_16(fileHeader.version); + int16 code = FROM_LE_16(fileHeader.id); + assert(offset == sizeof(VocFileHeader)); + // 0x100 is an invalid VOC version used by German version of DOTT (Disk) and + // French version of Simon the Sorcerer 2 (CD) + assert(version == 0x010A || version == 0x0114 || version == 0x0100); + assert(code == ~version + 0x1234); + + int len; + byte *ret_sound = 0; + size = 0; + begin_loop = 0; + end_loop = 0; + + while ((code = stream.readByte())) { + len = stream.readByte(); + len |= stream.readByte() << 8; + len |= stream.readByte() << 16; + + debug(2, "Block code %d, len %d", code, len); + + switch (code) { + case 1: + case 9: { + int packing; + if (code == 1) { + int time_constant = stream.readByte(); + packing = stream.readByte(); + len -= 2; + rate = getSampleRateFromVOCRate(time_constant); + } else { + rate = stream.readUint32LE(); + int bits = stream.readByte(); + int channels = stream.readByte(); + if (bits != 8 || channels != 1) { + warning("Unsupported VOC file format (%d bits per sample, %d channels)", bits, channels); + break; + } + packing = stream.readUint16LE(); + stream.readUint32LE(); + len -= 12; + } + debug(9, "VOC Data Block: %d, %d, %d", rate, packing, len); + if (packing == 0) { + if (size) { + ret_sound = (byte *)realloc(ret_sound, size + len); + } else { + ret_sound = (byte *)malloc(len); + } + stream.read(ret_sound + size, len); + size += len; + begin_loop = size; + end_loop = size; + } else { + warning("VOC file packing %d unsupported", packing); + } + } break; + case 3: // silence + // occur with a few Igor sounds, voc file starts with a silence block with a + // frequency different from the data block. Just ignore fow now (implementing + // it wouldn't make a big difference anyway...) + assert(len == 3); + stream.readUint16LE(); + stream.readByte(); + break; + case 6: // begin of loop + assert(len == 2); + loops = stream.readUint16LE(); + break; + case 7: // end of loop + assert(len == 0); + break; + case 8: { // "Extended" + // This occures in the LoL Intro demo. + // This block overwrites the next parameters of a block 1 "Sound data". + // To assure we never get any bad data here, we will assert in case + // this tries to define a stereo sound block or tries to use something + // different than 8bit unsigned sound data. + // TODO: Actually we would need to check the frequency divisor (the + // first word) here too. It is used in the following equation: + // sampleRate = 256000000/(channels * (65536 - frequencyDivisor)) + assert(len == 4); + stream.readUint16LE(); + uint8 codec = stream.readByte(); + uint8 channels = stream.readByte() + 1; + assert(codec == 0 && channels == 1); + } break; + default: + warning("Unhandled code %d in VOC file (len %d)", code, len); + return ret_sound; + } + } + debug(4, "VOC Data Size : %d", size); + return ret_sound; +} + +byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate) { + int loops, begin_loop, end_loop; + return loadVOCFromStream(stream, size, rate, loops, begin_loop, end_loop); +} + + +#ifdef STREAM_AUDIO_FROM_DISK + +int parseVOCFormat(Common::SeekableReadStream& stream, RawStreamBlock* block, int &rate, int &loops, int &begin_loop, int &end_loop) { + VocFileHeader fileHeader; + int currentBlock = 0; + int size = 0; + + debug(2, "parseVOCFormat"); + + if (stream.read(&fileHeader, 8) != 8) + goto invalid; + + if (!memcmp(&fileHeader, "VTLK", 4)) { + if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader)) + goto invalid; + } else if (!memcmp(&fileHeader, "Creative", 8)) { + if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8) + goto invalid; + } else { + invalid:; + warning("loadVOCFromStream: Invalid header"); + return 0; + } + + if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0) + error("loadVOCFromStream: Invalid header"); + if (fileHeader.desc[19] != 0x1A) + debug(3, "loadVOCFromStream: Partially invalid header"); + + int32 offset = FROM_LE_16(fileHeader.datablock_offset); + int16 version = FROM_LE_16(fileHeader.version); + int16 code = FROM_LE_16(fileHeader.id); + assert(offset == sizeof(VocFileHeader)); + // 0x100 is an invalid VOC version used by German version of DOTT (Disk) and + // French version of Simon the Sorcerer 2 (CD) + assert(version == 0x010A || version == 0x0114 || version == 0x0100); + assert(code == ~version + 0x1234); + + int len; + size = 0; + begin_loop = 0; + end_loop = 0; + + while ((code = stream.readByte())) { + len = stream.readByte(); + len |= stream.readByte() << 8; + len |= stream.readByte() << 16; + + debug(2, "Block code %d, len %d", code, len); + + switch (code) { + case 1: + case 9: { + int packing; + if (code == 1) { + int time_constant = stream.readByte(); + packing = stream.readByte(); + len -= 2; + rate = getSampleRateFromVOCRate(time_constant); + } else { + rate = stream.readUint32LE(); + int bits = stream.readByte(); + int channels = stream.readByte(); + if (bits != 8 || channels != 1) { + warning("Unsupported VOC file format (%d bits per sample, %d channels)", bits, channels); + break; + } + packing = stream.readUint16LE(); + stream.readUint32LE(); + len -= 12; + } + debug(9, "VOC Data Block: %d, %d, %d", rate, packing, len); + if (packing == 0) { + + // Found a data block - so add it to the block list + block[currentBlock].pos = stream.pos(); + block[currentBlock].len = len; + currentBlock++; + + stream.seek(len, SEEK_CUR); + + size += len; + begin_loop = size; + end_loop = size; + } else { + warning("VOC file packing %d unsupported", packing); + } + } break; + case 3: // silence + // occur with a few Igor sounds, voc file starts with a silence block with a + // frequency different from the data block. Just ignore fow now (implementing + // it wouldn't make a big difference anyway...) + assert(len == 3); + stream.readUint16LE(); + stream.readByte(); + break; + case 6: // begin of loop + assert(len == 2); + loops = stream.readUint16LE(); + break; + case 7: // end of loop + assert(len == 0); + break; + case 8: // "Extended" + // This occures in the LoL Intro demo. This block can usually be used to create stereo + // sound, but the LoL intro has only an empty block, thus this dummy implementation will + // work. + assert(len == 4); + stream.readUint16LE(); + stream.readByte(); + stream.readByte(); + break; + default: + warning("Unhandled code %d in VOC file (len %d)", code, len); + return 0; + } + } + debug(4, "VOC Data Size : %d", size); + return currentBlock; +} + +AudioStream *makeVOCDiskStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) { + const int MAX_AUDIO_BLOCKS = 256; + + RawStreamBlock *block = new RawStreamBlock[MAX_AUDIO_BLOCKS]; + int rate, loops, begin_loop, end_loop; + + int numBlocks = parseVOCFormat(*stream, block, rate, loops, begin_loop, end_loop); + + AudioStream *audioStream = 0; + + // Create an audiostream from the data. Note the numBlocks may be 0, + // e.g. when invalid data is encountered. See bug #2890038. + if (numBlocks) + audioStream = makeRawDiskStream_OLD(stream, block, numBlocks, rate, flags, disposeAfterUse/*, begin_loop, end_loop*/); + + delete[] block; + + return audioStream; +} + +SeekableAudioStream *makeVOCDiskStreamNoLoop(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) { + const int MAX_AUDIO_BLOCKS = 256; + + RawStreamBlock *block = new RawStreamBlock[MAX_AUDIO_BLOCKS]; + int rate, loops, begin_loop, end_loop; + + int numBlocks = parseVOCFormat(*stream, block, rate, loops, begin_loop, end_loop); + + SeekableAudioStream *audioStream = 0; + + // Create an audiostream from the data. Note the numBlocks may be 0, + // e.g. when invalid data is encountered. See bug #2890038. + if (numBlocks) + audioStream = makeRawDiskStream_OLD(stream, block, numBlocks, rate, flags, disposeAfterUse); + + delete[] block; + + return audioStream; +} + +#endif + + +AudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, uint loopStart, uint loopEnd, DisposeAfterUse::Flag disposeAfterUse) { +#ifdef STREAM_AUDIO_FROM_DISK + return makeVOCDiskStream(stream, flags, disposeAfterUse); +#else + int size, rate; + + byte *data = loadVOCFromStream(*stream, size, rate); + + if (!data) { + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + return 0; + } + + SeekableAudioStream *s = Audio::makeRawStream(data, size, rate, flags); + + if (loopStart != loopEnd) { + const bool isStereo = (flags & Audio::FLAG_STEREO) != 0; + const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0; + + if (loopEnd == 0) + loopEnd = size; + assert(loopStart <= loopEnd); + assert(loopEnd <= (uint)size); + + // Verify the buffer sizes are sane + if (is16Bit && isStereo) + assert((loopStart & 3) == 0 && (loopEnd & 3) == 0); + else if (is16Bit || isStereo) + assert((loopStart & 1) == 0 && (loopEnd & 1) == 0); + + const uint32 extRate = s->getRate() * (is16Bit ? 2 : 1) * (isStereo ? 2 : 1); + + return new SubLoopingAudioStream(s, 0, Timestamp(0, loopStart, extRate), Timestamp(0, loopEnd, extRate)); + } else { + return s; + } +#endif +} + +SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) { +#ifdef STREAM_AUDIO_FROM_DISK + return makeVOCDiskStreamNoLoop(stream, flags, disposeAfterUse); +#else + int size, rate; + + byte *data = loadVOCFromStream(*stream, size, rate); + + if (!data) { + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + return 0; + } + + return makeRawStream(data, size, rate, flags); +#endif +} + +} // End of namespace Audio diff --git a/audio/decoders/voc.h b/audio/decoders/voc.h new file mode 100644 index 0000000000..82cc261f2c --- /dev/null +++ b/audio/decoders/voc.h @@ -0,0 +1,107 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - agos + * - drascula + * - kyra + * - made + * - saga + * - scumm + * - touche + */ + +#ifndef SOUND_VOC_H +#define SOUND_VOC_H + +#include "common/scummsys.h" +#include "common/types.h" + +namespace Common { class ReadStream; } +namespace Common { class SeekableReadStream; } + +namespace Audio { + +class AudioStream; +class SeekableAudioStream; + + +#include "common/pack-start.h" // START STRUCT PACKING + +struct VocFileHeader { + uint8 desc[20]; + uint16 datablock_offset; + uint16 version; + uint16 id; +} PACKED_STRUCT; + +struct VocBlockHeader { + uint8 blocktype; + uint8 size[3]; + uint8 sr; + uint8 pack; +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + +/** + * Take a sample rate parameter as it occurs in a VOC sound header, and + * return the corresponding sample frequency. + * + * This method has special cases for the standard rates of 11025 and 22050 kHz, + * which due to limitations of the format, cannot be encoded exactly in a VOC + * file. As a consequence, many game files have sound data sampled with those + * rates, but the VOC marks them incorrectly as 11111 or 22222 kHz. This code + * works around that and "unrounds" the sampling rates. + */ +extern int getSampleRateFromVOCRate(int vocSR); + +/** + * Try to load a VOC from the given stream. Returns a pointer to memory + * containing the PCM sample data (allocated with malloc). It is the callers + * responsibility to dellocate that data again later on! Currently this + * function only supports uncompressed raw PCM data. + */ +extern byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate); + +/** + * Try to load a VOC from the given seekable stream and create an AudioStream + * from that data. Currently this function only supports uncompressed raw PCM + * data. Optionally supports (infinite) looping of a portion of the data. + * + * This function uses loadVOCFromStream() internally. + */ +AudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags = 0, uint loopStart = 0, uint loopEnd = 0, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::NO); + +/** + * This does not use any of the looping features of VOC files! + */ +SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/vorbis.cpp b/audio/decoders/vorbis.cpp new file mode 100644 index 0000000000..dc37e852d3 --- /dev/null +++ b/audio/decoders/vorbis.cpp @@ -0,0 +1,262 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Disable symbol overrides for FILE and fseek as those are used in the +// Vorbis headers. +#define FORBIDDEN_SYMBOL_EXCEPTION_FILE +#define FORBIDDEN_SYMBOL_EXCEPTION_fseek + +#include "audio/decoders/vorbis.h" + +#ifdef USE_VORBIS + +#include "common/debug.h" +#include "common/stream.h" +#include "common/util.h" + +#include "audio/audiostream.h" + +#ifdef USE_TREMOR +#if defined(__GP32__) // custom libtremor locations +#include +#else +#include +#endif +#else +#include +#endif + + +namespace Audio { + +// These are wrapper functions to allow using a SeekableReadStream object to +// provide data to the OggVorbis_File object. + +static size_t read_stream_wrap(void *ptr, size_t size, size_t nmemb, void *datasource) { + Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource; + + uint32 result = stream->read(ptr, size * nmemb); + + return result / size; +} + +static int seek_stream_wrap(void *datasource, ogg_int64_t offset, int whence) { + Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource; + stream->seek((int32)offset, whence); + return stream->pos(); +} + +static int close_stream_wrap(void *datasource) { + // Do nothing -- we leave it up to the VorbisStream to free memory as appropriate. + return 0; +} + +static long tell_stream_wrap(void *datasource) { + Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource; + return stream->pos(); +} + +static ov_callbacks g_stream_wrap = { + read_stream_wrap, seek_stream_wrap, close_stream_wrap, tell_stream_wrap +}; + + + +#pragma mark - +#pragma mark --- Ogg Vorbis stream --- +#pragma mark - + + +class VorbisStream : public SeekableAudioStream { +protected: + Common::SeekableReadStream *_inStream; + DisposeAfterUse::Flag _disposeAfterUse; + + bool _isStereo; + int _rate; + + Timestamp _length; + + OggVorbis_File _ovFile; + + int16 _buffer[4096]; + const int16 *_bufferEnd; + const int16 *_pos; + +public: + // startTime / duration are in milliseconds + VorbisStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose); + ~VorbisStream(); + + int readBuffer(int16 *buffer, const int numSamples); + + bool endOfData() const { return _pos >= _bufferEnd; } + bool isStereo() const { return _isStereo; } + int getRate() const { return _rate; } + + bool seek(const Timestamp &where); + Timestamp getLength() const { return _length; } +protected: + bool refill(); +}; + +VorbisStream::VorbisStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) : + _inStream(inStream), + _disposeAfterUse(dispose), + _length(0, 1000), + _bufferEnd(_buffer + ARRAYSIZE(_buffer)) { + + int res = ov_open_callbacks(inStream, &_ovFile, NULL, 0, g_stream_wrap); + if (res < 0) { + warning("Could not create Vorbis stream (%d)", res); + _pos = _bufferEnd; + return; + } + + // Read in initial data + if (!refill()) + return; + + // Setup some header information + _isStereo = ov_info(&_ovFile, -1)->channels >= 2; + _rate = ov_info(&_ovFile, -1)->rate; + +#ifdef USE_TREMOR + _length = Timestamp(ov_time_total(&_ovFile, -1), getRate()); +#else + _length = Timestamp(uint32(ov_time_total(&_ovFile, -1) * 1000.0), getRate()); +#endif +} + +VorbisStream::~VorbisStream() { + ov_clear(&_ovFile); + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _inStream; +} + +int VorbisStream::readBuffer(int16 *buffer, const int numSamples) { + int samples = 0; + while (samples < numSamples && _pos < _bufferEnd) { + const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos)); + memcpy(buffer, _pos, len * 2); + buffer += len; + _pos += len; + samples += len; + if (_pos >= _bufferEnd) { + if (!refill()) + break; + } + } + return samples; +} + +bool VorbisStream::seek(const Timestamp &where) { + // Vorbisfile uses the sample pair number, thus we always use "false" for the isStereo parameter + // of the convertTimeToStreamPos helper. + int res = ov_pcm_seek(&_ovFile, convertTimeToStreamPos(where, getRate(), false).totalNumberOfFrames()); + if (res) { + warning("Error seeking in Vorbis stream (%d)", res); + _pos = _bufferEnd; + return false; + } + + return refill(); +} + +bool VorbisStream::refill() { + // Read the samples + uint len_left = sizeof(_buffer); + char *read_pos = (char *)_buffer; + + while (len_left > 0) { + long result; + +#ifdef USE_TREMOR + // Tremor ov_read() always returns data as signed 16 bit interleaved PCM + // in host byte order. As such, it does not take arguments to request + // specific signedness, byte order or bit depth as in Vorbisfile. + result = ov_read(&_ovFile, read_pos, len_left, + NULL); +#else +#ifdef SCUMM_BIG_ENDIAN + result = ov_read(&_ovFile, read_pos, len_left, + 1, + 2, // 16 bit + 1, // signed + NULL); +#else + result = ov_read(&_ovFile, read_pos, len_left, + 0, + 2, // 16 bit + 1, // signed + NULL); +#endif +#endif + if (result == OV_HOLE) { + // Possibly recoverable, just warn about it + warning("Corrupted data in Vorbis file"); + } else if (result == 0) { + //warning("End of file while reading from Vorbis file"); + //_pos = _bufferEnd; + //return false; + break; + } else if (result < 0) { + warning("Error reading from Vorbis stream (%d)", int(result)); + _pos = _bufferEnd; + // Don't delete it yet, that causes problems in + // the CD player emulation code. + return false; + } else { + len_left -= result; + read_pos += result; + } + } + + _pos = _buffer; + _bufferEnd = (int16 *)read_pos; + + return true; +} + + +#pragma mark - +#pragma mark --- Ogg Vorbis factory functions --- +#pragma mark - + +SeekableAudioStream *makeVorbisStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse) { + SeekableAudioStream *s = new VorbisStream(stream, disposeAfterUse); + if (s && s->endOfData()) { + delete s; + return 0; + } else { + return s; + } +} + +} // End of namespace Audio + +#endif // #ifdef USE_VORBIS diff --git a/audio/decoders/vorbis.h b/audio/decoders/vorbis.h new file mode 100644 index 0000000000..7cc395cccb --- /dev/null +++ b/audio/decoders/vorbis.h @@ -0,0 +1,75 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - agos + * - draci + * - kyra + * - m4 + * - queen + * - saga + * - sci + * - scumm + * - sword1 + * - sword2 + * - touche + * - tucker + */ + +#ifndef SOUND_VORBIS_H +#define SOUND_VORBIS_H + +#include "common/scummsys.h" +#include "common/types.h" + +#ifdef USE_VORBIS + +namespace Common { + class SeekableReadStream; +} + +namespace Audio { + +class AudioStream; +class SeekableAudioStream; + +/** + * Create a new SeekableAudioStream from the Ogg Vorbis data in the given stream. + * Allows for seeking (which is why we require a SeekableReadStream). + * + * @param stream the SeekableReadStream from which to read the Ogg Vorbis data + * @param disposeAfterUse whether to delete the stream after use + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *makeVorbisStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif // #ifdef USE_VORBIS +#endif // #ifndef SOUND_VORBIS_H diff --git a/audio/decoders/wave.cpp b/audio/decoders/wave.cpp new file mode 100644 index 0000000000..1f0ddd8ceb --- /dev/null +++ b/audio/decoders/wave.cpp @@ -0,0 +1,194 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/debug.h" +#include "common/util.h" +#include "common/stream.h" + +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/wave.h" +#include "audio/decoders/adpcm.h" +#include "audio/decoders/raw.h" + +namespace Audio { + +bool loadWAVFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags, uint16 *wavType, int *blockAlign_) { + const int32 initialPos = stream.pos(); + byte buf[4+1]; + + buf[4] = 0; + + stream.read(buf, 4); + if (memcmp(buf, "RIFF", 4) != 0) { + warning("getWavInfo: No 'RIFF' header"); + return false; + } + + int32 wavLength = stream.readUint32LE(); + + stream.read(buf, 4); + if (memcmp(buf, "WAVE", 4) != 0) { + warning("getWavInfo: No 'WAVE' header"); + return false; + } + + stream.read(buf, 4); + if (memcmp(buf, "fmt ", 4) != 0) { + warning("getWavInfo: No 'fmt' header"); + return false; + } + + uint32 fmtLength = stream.readUint32LE(); + if (fmtLength < 16) { + // A valid fmt chunk always contains at least 16 bytes + warning("getWavInfo: 'fmt' header is too short"); + return false; + } + + // Next comes the "type" field of the fmt header. Some typical + // values for it: + // 1 -> uncompressed PCM + // 17 -> IMA ADPCM compressed WAVE + // See for a more complete + // list of common WAVE compression formats... + uint16 type = stream.readUint16LE(); // == 1 for PCM data + uint16 numChannels = stream.readUint16LE(); // 1 for mono, 2 for stereo + uint32 samplesPerSec = stream.readUint32LE(); // in Hz + uint32 avgBytesPerSec = stream.readUint32LE(); // == SampleRate * NumChannels * BitsPerSample/8 + + uint16 blockAlign = stream.readUint16LE(); // == NumChannels * BitsPerSample/8 + uint16 bitsPerSample = stream.readUint16LE(); // 8, 16 ... + // 8 bit data is unsigned, 16 bit data signed + + + if (wavType != 0) + *wavType = type; + + if (blockAlign_ != 0) + *blockAlign_ = blockAlign; +#if 0 + debug("WAVE information:"); + debug(" total size: %d", wavLength); + debug(" fmt size: %d", fmtLength); + debug(" type: %d", type); + debug(" numChannels: %d", numChannels); + debug(" samplesPerSec: %d", samplesPerSec); + debug(" avgBytesPerSec: %d", avgBytesPerSec); + debug(" blockAlign: %d", blockAlign); + debug(" bitsPerSample: %d", bitsPerSample); +#endif + + if (type != 1 && type != 2 && type != 17) { + warning("getWavInfo: only PCM, MS ADPCM or IMA ADPCM data is supported (type %d)", type); + return false; + } + + if (blockAlign != numChannels * bitsPerSample / 8 && type != 2) { + debug(0, "getWavInfo: blockAlign is invalid"); + } + + if (avgBytesPerSec != samplesPerSec * blockAlign && type != 2) { + debug(0, "getWavInfo: avgBytesPerSec is invalid"); + } + + // Prepare the return values. + rate = samplesPerSec; + + flags = 0; + if (bitsPerSample == 8) // 8 bit data is unsigned + flags |= Audio::FLAG_UNSIGNED; + else if (bitsPerSample == 16) // 16 bit data is signed little endian + flags |= (Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN); + else if (bitsPerSample == 4 && (type == 2 || type == 17)) + flags |= Audio::FLAG_16BITS; + else { + warning("getWavInfo: unsupported bitsPerSample %d", bitsPerSample); + return false; + } + + if (numChannels == 2) + flags |= Audio::FLAG_STEREO; + else if (numChannels != 1) { + warning("getWavInfo: unsupported number of channels %d", numChannels); + return false; + } + + // It's almost certainly a WAV file, but we still need to find its + // 'data' chunk. + + // Skip over the rest of the fmt chunk. + int offset = fmtLength - 16; + + do { + stream.seek(offset, SEEK_CUR); + if (stream.pos() >= initialPos + wavLength + 8) { + warning("getWavInfo: Can't find 'data' chunk"); + return false; + } + stream.read(buf, 4); + offset = stream.readUint32LE(); + +#if 0 + debug(" found a '%s' tag of size %d", buf, offset); +#endif + } while (memcmp(buf, "data", 4) != 0); + + // Stream now points at 'offset' bytes of sample data... + size = offset; + + return true; +} + +RewindableAudioStream *makeWAVStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) { + int size, rate; + byte flags; + uint16 type; + int blockAlign; + + if (!loadWAVFromStream(*stream, size, rate, flags, &type, &blockAlign)) { + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + return 0; + } + + if (type == 17) // MS IMA ADPCM + return makeADPCMStream(stream, disposeAfterUse, size, Audio::kADPCMMSIma, rate, (flags & Audio::FLAG_STEREO) ? 2 : 1, blockAlign); + else if (type == 2) // MS ADPCM + return makeADPCMStream(stream, disposeAfterUse, size, Audio::kADPCMMS, rate, (flags & Audio::FLAG_STEREO) ? 2 : 1, blockAlign); + + // Raw PCM. Just read everything at once. + // TODO: More elegant would be to wrap the stream. + byte *data = (byte *)malloc(size); + assert(data); + stream->read(data, size); + + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + + return makeRawStream(data, size, rate, flags); +} + +} // End of namespace Audio diff --git a/audio/decoders/wave.h b/audio/decoders/wave.h new file mode 100644 index 0000000000..2bdbe8f0b6 --- /dev/null +++ b/audio/decoders/wave.h @@ -0,0 +1,84 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - agos + * - gob + * - mohawk + * - saga + * - sci + * - scumm + * - sword1 + * - sword2 + * - tucker + */ + +#ifndef SOUND_WAVE_H +#define SOUND_WAVE_H + +#include "common/scummsys.h" +#include "common/types.h" + +namespace Common { class SeekableReadStream; } + +namespace Audio { + +class RewindableAudioStream; + +/** + * Try to load a WAVE from the given seekable stream. Returns true if + * successful. In that case, the stream's seek position will be set to the + * start of the audio data, and size, rate and flags contain information + * necessary for playback. Currently this function supports uncompressed + * raw PCM data, MS IMA ADPCM and MS ADPCM (uses makeADPCMStream internally). + */ +extern bool loadWAVFromStream( + Common::SeekableReadStream &stream, + int &size, + int &rate, + byte &flags, + uint16 *wavType = 0, + int *blockAlign = 0); + +/** + * Try to load a WAVE from the given seekable stream and create an AudioStream + * from that data. Currently this function supports uncompressed + * raw PCM data, MS IMA ADPCM and MS ADPCM (uses makeADPCMStream internally). + * + * This function uses loadWAVFromStream() internally. + * + * @param stream the SeekableReadStream from which to read the WAVE data + * @param disposeAfterUse whether to delete the stream after use + * @return a new RewindableAudioStream, or NULL, if an error occurred + */ +RewindableAudioStream *makeWAVStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif diff --git a/audio/fmopl.cpp b/audio/fmopl.cpp new file mode 100644 index 0000000000..1f61e16101 --- /dev/null +++ b/audio/fmopl.cpp @@ -0,0 +1,197 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "audio/fmopl.h" + +#include "audio/softsynth/opl/dosbox.h" +#include "audio/softsynth/opl/mame.h" + +#include "common/config-manager.h" +#include "common/translation.h" + +namespace OPL { + +// Config implementation + +enum OplEmulator { + kAuto = 0, + kMame = 1, + kDOSBox = 2 +}; + +OPL::OPL() { + if (_hasInstance) + error("There are multiple OPL output instances running"); + _hasInstance = true; +} + +const Config::EmulatorDescription Config::_drivers[] = { + { "auto", "", kAuto, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 }, + { "mame", _s("MAME OPL emulator"), kMame, kFlagOpl2 }, +#ifndef DISABLE_DOSBOX_OPL + { "db", _s("DOSBox OPL emulator"), kDOSBox, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 }, +#endif + { 0, 0, 0, 0 } +}; + +Config::DriverId Config::parse(const Common::String &name) { + for (int i = 0; _drivers[i].name; ++i) { + if (name.equalsIgnoreCase(_drivers[i].name)) + return _drivers[i].id; + } + + return -1; +} + +Config::DriverId Config::detect(OplType type) { + uint32 flags = 0; + switch (type) { + case kOpl2: + flags = kFlagOpl2; + break; + + case kDualOpl2: + flags = kFlagDualOpl2; + break; + + case kOpl3: + flags = kFlagOpl3; + break; + } + + DriverId drv = parse(ConfMan.get("opl_driver")); + + // When a valid driver is selected, check whether it supports + // the requested OPL chip. + if (drv != -1 && drv != kAuto) { + // If the chip is supported, just use the driver. + if ((flags & _drivers[drv].flags)) { + return drv; + } else { + // Else we will output a warning and just + // return that no valid driver is found. + warning("Your selected OPL driver \"%s\" does not support type %d emulation, which is requested by your game", _drivers[drv].description, type); + return -1; + } + } + + // Detect the first matching emulator + drv = -1; + + for (int i = 1; _drivers[i].name; ++i) { + if (_drivers[i].flags & flags) { + drv = _drivers[i].id; + break; + } + } + + return drv; +} + +OPL *Config::create(OplType type) { + return create(kAuto, type); +} + +OPL *Config::create(DriverId driver, OplType type) { + // On invalid driver selection, we try to do some fallback detection + if (driver == -1) { + warning("Invalid OPL driver selected, trying to detect a fallback emulator"); + driver = kAuto; + } + + // If autodetection is selected, we search for a matching + // driver. + if (driver == kAuto) { + driver = detect(type); + + // No emulator for the specified OPL chip could + // be found, thus stop here. + if (driver == -1) { + warning("No OPL emulator available for type %d", type); + return 0; + } + } + + switch (driver) { + case kMame: + if (type == kOpl2) + return new MAME::OPL(); + else + warning("MAME OPL emulator only supports OPL2 emulation"); + return 0; + +#ifndef DISABLE_DOSBOX_OPL + case kDOSBox: + return new DOSBox::OPL(type); +#endif + + default: + warning("Unsupported OPL emulator %d", driver); + // TODO: Maybe we should add some dummy emulator too, which just outputs + // silence as sound? + return 0; + } +} + +bool OPL::_hasInstance = false; + +} // End of namespace OPL + +void OPLDestroy(FM_OPL *OPL) { + delete OPL; +} + +void OPLResetChip(FM_OPL *OPL) { + OPL->reset(); +} + +void OPLWrite(FM_OPL *OPL, int a, int v) { + OPL->write(a, v); +} + +unsigned char OPLRead(FM_OPL *OPL, int a) { + return OPL->read(a); +} + +void OPLWriteReg(FM_OPL *OPL, int r, int v) { + OPL->writeReg(r, v); +} + +void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length) { + OPL->readBuffer(buffer, length); +} + +FM_OPL *makeAdLibOPL(int rate) { + FM_OPL *opl = OPL::Config::create(); + + if (opl) { + if (!opl->init(rate)) { + delete opl; + opl = 0; + } + } + + return opl; +} + diff --git a/audio/fmopl.h b/audio/fmopl.h new file mode 100644 index 0000000000..33235f3545 --- /dev/null +++ b/audio/fmopl.h @@ -0,0 +1,179 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef SOUND_FMOPL_H +#define SOUND_FMOPL_H + +#include "common/scummsys.h" +#include "common/str.h" + +namespace OPL { + +class OPL; + +class Config { +public: + enum OplFlags { + kFlagOpl2 = (1 << 0), + kFlagDualOpl2 = (1 << 1), + kFlagOpl3 = (1 << 2) + }; + + /** + * OPL type to emulate. + */ + enum OplType { + kOpl2, + kDualOpl2, + kOpl3 + }; + + typedef int8 DriverId; + struct EmulatorDescription { + const char *name; + const char *description; + + DriverId id; // A unique ID for each driver + uint32 flags; // Capabilities of this driver + }; + + /** + * Get a list of all available OPL emulators. + * @return list of all available OPL emulators, terminated by a zero entry + */ + static const EmulatorDescription *getAvailable() { return _drivers; } + + /** + * Returns the driver id of a given name. + */ + static DriverId parse(const Common::String &name); + + /** + * Detects a driver for the specific type. + * + * @return Returns a valid driver id on success, -1 otherwise. + */ + static DriverId detect(OplType type); + + /** + * Creates the specific driver with a specific type setup. + */ + static OPL *create(DriverId driver, OplType type); + + /** + * Wrapper to easily init an OPL chip, without specifing an emulator. + * By default it will try to initialize an OPL2 emulator, thus an AdLib card. + */ + static OPL *create(OplType type = kOpl2); + +private: + static const EmulatorDescription _drivers[]; +}; + +class OPL { +private: + static bool _hasInstance; +public: + OPL(); + virtual ~OPL() { _hasInstance = false; } + + /** + * Initializes the OPL emulator. + * + * @param rate output sample rate + * @return true on success, false on failure + */ + virtual bool init(int rate) = 0; + + /** + * Reinitializes the OPL emulator + */ + virtual void reset() = 0; + + /** + * Writes a byte to the given I/O port. + * + * @param a port address + * @param v value, which will be written + */ + virtual void write(int a, int v) = 0; + + /** + * Reads a byte from the given I/O port. + * + * @param a port address + * @return value read + */ + virtual byte read(int a) = 0; + + /** + * Function to directly write to a specific OPL register. + * This writes to *both* chips for a Dual OPL2. + * + * @param r hardware register number to write to + * @param v value, which will be written + */ + virtual void writeReg(int r, int v) = 0; + + /** + * Read up to 'length' samples. + * + * Data will be in native endianess, 16 bit per sample, signed. + * For stereo OPL, buffer will be filled with interleaved + * left and right channel samples, starting with a left sample. + * Furthermore, the samples in the left and right are summed up. + * So if you request 4 samples from a stereo OPL, you will get + * a total of two left channel and two right channel samples. + */ + virtual void readBuffer(int16 *buffer, int length) = 0; + + /** + * Returns whether the setup OPL mode is stereo or not + */ + virtual bool isStereo() const = 0; +}; + +} // End of namespace OPL + +// Legacy API +// !You should not write any new code using the legacy API! +typedef OPL::OPL FM_OPL; + +void OPLDestroy(FM_OPL *OPL); + +void OPLResetChip(FM_OPL *OPL); +void OPLWrite(FM_OPL *OPL, int a, int v); +unsigned char OPLRead(FM_OPL *OPL, int a); +void OPLWriteReg(FM_OPL *OPL, int r, int v); +void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length); + +/** + * Legacy factory to create an AdLib (OPL2) chip. + * + * !You should not write any new code using the legacy API! + */ +FM_OPL *makeAdLibOPL(int rate); + +#endif + diff --git a/audio/mididrv.cpp b/audio/mididrv.cpp new file mode 100644 index 0000000000..a1487ff69d --- /dev/null +++ b/audio/mididrv.cpp @@ -0,0 +1,326 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "engines/engine.h" +#include "common/config-manager.h" +#include "common/str.h" +#include "common/system.h" +#include "common/util.h" +#include "audio/mididrv.h" +#include "audio/musicplugin.h" +#include "common/translation.h" + +const byte MidiDriver::_mt32ToGm[128] = { +// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 0, 2, 4, 4, 5, 3, 16, 17, 18, 16, 16, 19, 20, 21, // 0x + 6, 6, 6, 7, 7, 7, 8, 112, 62, 62, 63, 63, 38, 38, 39, 39, // 1x + 88, 95, 52, 98, 97, 99, 14, 54, 102, 96, 53, 102, 81, 100, 14, 80, // 2x + 48, 48, 49, 45, 41, 40, 42, 42, 43, 46, 45, 24, 25, 28, 27, 104, // 3x + 32, 32, 34, 33, 36, 37, 35, 35, 79, 73, 72, 72, 74, 75, 64, 65, // 4x + 66, 67, 71, 71, 68, 69, 70, 22, 56, 59, 57, 57, 60, 60, 58, 61, // 5x + 61, 11, 11, 98, 14, 9, 14, 13, 12, 107, 107, 77, 78, 78, 76, 76, // 6x + 47, 117, 127, 118, 118, 116, 115, 119, 115, 112, 55, 124, 123, 0, 14, 117 // 7x +}; + +const byte MidiDriver::_gmToMt32[128] = { +// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 5, 1, 2, 7, 3, 5, 16, 21, 22, 101, 101, 97, 104, 103, 102, 20, // 0x + 8, 9, 11, 12, 14, 15, 87, 15, 59, 60, 61, 62, 67, 44, 79, 23, // 1x + 64, 67, 66, 70, 68, 69, 28, 31, 52, 54, 55, 56, 49, 51, 57, 112, // 2x + 48, 50, 45, 26, 34, 35, 45, 122, 89, 90, 94, 81, 92, 95, 24, 25, // 3x + 80, 78, 79, 78, 84, 85, 86, 82, 74, 72, 76, 77, 110, 107, 108, 76, // 4x + 47, 44, 111, 45, 44, 34, 44, 30, 32, 33, 88, 34, 35, 35, 38, 33, // 5x + 41, 36, 100, 37, 40, 34, 43, 40, 63, 21, 99, 105, 103, 86, 55, 84, // 6x + 101, 103, 100, 120, 117, 113, 99, 128, 128, 128, 128, 124, 123, 128, 128, 128, // 7x +}; + +static const uint32 GUIOMapping[] = { + MT_PCSPK, Common::GUIO_MIDIPCSPK, + MT_CMS, Common::GUIO_MIDICMS, + MT_PCJR, Common::GUIO_MIDIPCJR, + MT_ADLIB, Common::GUIO_MIDIADLIB, + MT_C64, Common::GUIO_MIDIC64, + MT_AMIGA, Common::GUIO_MIDIAMIGA, + MT_APPLEIIGS, Common::GUIO_MIDIAPPLEIIGS, + MT_TOWNS, Common::GUIO_MIDITOWNS, + MT_PC98, Common::GUIO_MIDIPC98, + MT_GM, Common::GUIO_MIDIGM, + MT_MT32, Common::GUIO_MIDIMT32, + 0, 0 +}; + +uint32 MidiDriver::musicType2GUIO(uint32 musicType) { + uint32 res = 0; + + for (int i = 0; GUIOMapping[i] || GUIOMapping[i + 1]; i += 2) { + if (musicType == GUIOMapping[i] || musicType == (uint32)-1) + res |= GUIOMapping[i + 1]; + } + + return res; +} + +bool MidiDriver::_forceTypeMT32 = false; + +MusicType MidiDriver::getMusicType(MidiDriver::DeviceHandle handle) { + if (_forceTypeMT32) + return MT_MT32; + + if (handle) { + const MusicPlugin::List p = MusicMan.getPlugins(); + for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); m++) { + MusicDevices i = (**m)->getDevices(); + for (MusicDevices::iterator d = i.begin(); d != i.end(); d++) { + if (handle == d->getHandle()) + return d->getMusicType(); + } + } + } + + return MT_INVALID; +} + +Common::String MidiDriver::getDeviceString(DeviceHandle handle, DeviceStringType type) { + if (handle) { + const MusicPlugin::List p = MusicMan.getPlugins(); + for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); m++) { + MusicDevices i = (**m)->getDevices(); + for (MusicDevices::iterator d = i.begin(); d != i.end(); d++) { + if (handle == d->getHandle()) { + if (type == kDriverName) + return d->getMusicDriverName(); + else if (type == kDriverId) + return d->getMusicDriverId(); + else if (type == kDeviceId) + return d->getCompleteId(); + else + return Common::String("auto"); + } + } + } + } + + return Common::String("auto"); +} + +MidiDriver::DeviceHandle MidiDriver::detectDevice(int flags) { + // Query the selected music device (defaults to MT_AUTO device). + DeviceHandle hdl = getDeviceHandle(ConfMan.get("music_driver")); + + _forceTypeMT32 = false; + + // Check whether the selected music driver is compatible with the + // given flags. + switch (getMusicType(hdl)) { + case MT_PCSPK: + if (flags & MDT_PCSPK) + return hdl; + break; + + case MT_PCJR: + if (flags & MDT_PCJR) + return hdl; + break; + + case MT_CMS: + if (flags & MDT_CMS) + return hdl; + break; + + case MT_ADLIB: + if (flags & MDT_ADLIB) + return hdl; + break; + + case MT_C64: + if (flags & MDT_C64) + return hdl; + break; + + case MT_AMIGA: + if (flags & MDT_AMIGA) + return hdl; + break; + + case MT_APPLEIIGS: + if (flags & MDT_APPLEIIGS) + return hdl; + break; + + case MT_TOWNS: + if (flags & MDT_TOWNS) + return hdl; + break; + + case MT_PC98: + if (flags & MDT_PC98) + return hdl; + break; + + case MT_GM: + case MT_GS: + case MT_MT32: + if (flags & MDT_MIDI) + return hdl; + break; + + case MT_NULL: + return hdl; + + default: + break; + } + + // If the selected driver did not match the flags setting, + // we try to determine a suitable and "optimal" music driver. + const MusicPlugin::List p = MusicMan.getPlugins(); + // If only MDT_MIDI but not MDT_PREFER_MT32 or MDT_PREFER_GM is set we prefer the other devices (which will always be + // detected since they are hard coded and cannot be disabled. + for (int l = (flags & (MDT_PREFER_GM | MDT_PREFER_MT32)) ? 1 : 0; l < 2; ++l) { + if ((flags & MDT_MIDI) && (l == 1)) { + // If a preferred MT32 or GM device has been selected that device gets returned + if (flags & MDT_PREFER_MT32) + hdl = getDeviceHandle(ConfMan.get("mt32_device")); + else if (flags & MDT_PREFER_GM) + hdl = getDeviceHandle(ConfMan.get("gm_device")); + else + hdl = getDeviceHandle("auto"); + + const MusicType type = getMusicType(hdl); + + // If have a "Don't use GM/MT-32" setting we skip this part and jump + // to AdLib, PC Speaker etc. detection right away. + if (type != MT_NULL) { + if (type != MT_AUTO && type != MT_INVALID) { + if (flags & MDT_PREFER_MT32) + // If we have a preferred MT32 device we disable the gm/mt32 mapping (more about this in mididrv.h) + _forceTypeMT32 = true; + + return hdl; + } + + // If we have no specific device selected (neither in the scummvm nor in the game domain) + // and no preferred MT32 or GM device selected we arrive here. + // If MT32 is preferred we try for the first available device with music type 'MT_MT32' (usually the mt32 emulator) + if (flags & MDT_PREFER_MT32) { + for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); ++m) { + MusicDevices i = (**m)->getDevices(); + for (MusicDevices::iterator d = i.begin(); d != i.end(); ++d) { + if (d->getMusicType() == MT_MT32) + return d->getHandle(); + } + } + } + + // Now we default to the first available device with music type 'MT_GM' + for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); ++m) { + MusicDevices i = (**m)->getDevices(); + for (MusicDevices::iterator d = i.begin(); d != i.end(); ++d) { + if (d->getMusicType() == MT_GM || d->getMusicType() == MT_GS) + return d->getHandle(); + } + } + } + } + + MusicType tp = MT_AUTO; + if (flags & MDT_TOWNS) + tp = MT_TOWNS; + else if (flags & MDT_PC98) + tp = MT_PC98; + else if (flags & MDT_ADLIB) + tp = MT_ADLIB; + else if (flags & MDT_PCJR) + tp = MT_PCJR; + else if (flags & MDT_PCSPK) + tp = MT_PCSPK; + else if (flags & MDT_C64) + tp = MT_C64; + else if (flags & MDT_AMIGA) + tp = MT_AMIGA; + else if (flags & MDT_APPLEIIGS) + tp = MT_APPLEIIGS; + else if (l == 0) + // if we haven't tried to find a MIDI device yet we do this now. + continue; + else + tp = MT_AUTO; + + for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); ++m) { + MusicDevices i = (**m)->getDevices(); + for (MusicDevices::iterator d = i.begin(); d != i.end(); ++d) { + if (d->getMusicType() == tp) + return d->getHandle(); + } + } + } + + return 0; +} + +MidiDriver *MidiDriver::createMidi(MidiDriver::DeviceHandle handle) { + MidiDriver *driver = 0; + const MusicPlugin::List p = MusicMan.getPlugins(); + for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); m++) { + if (getDeviceString(handle, MidiDriver::kDriverId).equals((**m)->getId())) + (**m)->createInstance(&driver, handle); + } + + return driver; +} + +MidiDriver::DeviceHandle MidiDriver::getDeviceHandle(const Common::String &identifier) { + const MusicPlugin::List p = MusicMan.getPlugins(); + + if (p.begin() == p.end()) + error("Music plugins must be loaded prior to calling this method"); + + for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); m++) { + MusicDevices i = (**m)->getDevices(); + for (MusicDevices::iterator d = i.begin(); d != i.end(); d++) { + // The music driver id isn't unique, but it will match + // driver's first device. This is useful when selecting + // the driver from the command line. + if (identifier.equals(d->getMusicDriverId()) || identifier.equals(d->getCompleteId()) || identifier.equals(d->getCompleteName())) { + return d->getHandle(); + } + } + } + + return 0; +} + +void MidiDriver::sendMT32Reset() { + static const byte resetSysEx[] = { 0x41, 0x10, 0x16, 0x12, 0x7F, 0x00, 0x00, 0x01, 0x00 }; + sysEx(resetSysEx, sizeof(resetSysEx)); + g_system->delayMillis(100); +} + +void MidiDriver::sendGMReset() { + static const byte resetSysEx[] = { 0x7E, 0x7F, 0x09, 0x01 }; + sysEx(resetSysEx, sizeof(resetSysEx)); + g_system->delayMillis(100); +} + diff --git a/audio/mididrv.h b/audio/mididrv.h new file mode 100644 index 0000000000..9e649cba3d --- /dev/null +++ b/audio/mididrv.h @@ -0,0 +1,288 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SOUND_MIDIDRV_H +#define SOUND_MIDIDRV_H + +#include "common/scummsys.h" +#include "common/timer.h" + +class MidiChannel; +class MusicDevice; + +namespace Audio { + class Mixer; +} +namespace Common { class String; } + +/** + * Music Driver Types, used to uniquely identify each music driver. + * + * The pseudo drivers are listed first, then all native drivers, + * then all other MIDI drivers, and finally the non-MIDI drivers. + * + * @todo Rename MidiDriverType to MusicDriverType + */ + +/** + * Music types that music drivers can implement and engines can rely on. + */ +enum MusicType { + MT_INVALID = -1, // Invalid output + MT_AUTO = 0, // Auto + MT_NULL, // Null + MT_PCSPK, // PC Speaker + MT_PCJR, // PCjr + MT_CMS, // CMS + MT_ADLIB, // AdLib + MT_C64, // C64 + MT_AMIGA, // Amiga + MT_APPLEIIGS, // Apple IIGS + MT_TOWNS, // FM-TOWNS + MT_PC98, // PC98 + MT_GM, // General MIDI + MT_MT32, // MT-32 + MT_GS // Roland GS +}; + +/** + * A set of flags to be passed to detectDevice() which can be used to + * specify what kind of music driver is preferred / accepted. + * + * The flags (except for MDT_PREFER_MT32 and MDT_PREFER_GM) indicate whether a given driver + * type is acceptable. E.g. the TOWNS music driver could be returned by + * detectDevice if and only if MDT_TOWNS is specified. + * + * @todo Rename MidiDriverFlags to MusicDriverFlags + */ +enum MidiDriverFlags { + MDT_NONE = 0, + MDT_PCSPK = 1 << 0, // PC Speaker: Maps to MD_PCSPK and MD_PCJR + MDT_CMS = 1 << 1, // Creative Music System / Gameblaster: Maps to MD_CMS + MDT_PCJR = 1 << 2, // Tandy/PC Junior driver + MDT_ADLIB = 1 << 3, // AdLib: Maps to MT_ADLIB + MDT_C64 = 1 << 4, + MDT_AMIGA = 1 << 5, + MDT_APPLEIIGS = 1 << 6, + MDT_TOWNS = 1 << 7, // FM-TOWNS: Maps to MT_TOWNS + MDT_PC98 = 1 << 8, // FM-TOWNS: Maps to MT_PC98 + MDT_MIDI = 1 << 9, // Real MIDI + MDT_PREFER_MT32 = 1 << 10, // MT-32 output is preferred + MDT_PREFER_GM = 1 << 11 // GM output is preferred +}; + +/** + * Abstract description of a MIDI driver. Used by the config file and command + * line parsing code, and also to be able to give the user a list of available + * drivers. + * + * @todo Rename MidiDriverType to MusicDriverType + */ + +/** + * Abstract MIDI Driver Class + * + * @todo Rename MidiDriver to MusicDriver + */ +class MidiDriver { +public: + /** + * The device handle. + * + * The value 0 is reserved for an invalid device for now. + * TODO: Maybe we should use -1 (i.e. 0xFFFFFFFF) as + * invalid device? + */ + typedef uint32 DeviceHandle; + + enum DeviceStringType { + kDriverName, + kDriverId, + kDeviceId + }; + + static uint32 musicType2GUIO(uint32 musicType); + + /** Create music driver matching the given device handle, or NULL if there is no match. */ + static MidiDriver *createMidi(DeviceHandle handle); + + /** Returns device handle based on the present devices and the flags parameter. */ + static DeviceHandle detectDevice(int flags); + + /** Find the music driver matching the given driver name/description. */ + static DeviceHandle getDeviceHandle(const Common::String &identifier); + + /** Get the music type matching the given device handle, or MT_AUTO if there is no match. */ + static MusicType getMusicType(DeviceHandle handle); + + /** Get the device description string matching the given device handle and the given type. */ + static Common::String getDeviceString(DeviceHandle handle, DeviceStringType type); + +private: + // If detectDevice() detects MT32 and we have a preferred MT32 device + // we use this to force getMusicType() to return MT_MT32 so that we don't + // have to rely on the 'True Roland MT-32' config manager setting (since nobody + // would possibly think about activating 'True Roland MT-32' when he has set + // 'Music Driver' to '') + static bool _forceTypeMT32; + +public: + virtual ~MidiDriver() { } + + static const byte _mt32ToGm[128]; + static const byte _gmToMt32[128]; + + /** + * Error codes returned by open. + * Can be converted to a string with getErrorName(). + */ + enum { + MERR_CANNOT_CONNECT = 1, +// MERR_STREAMING_NOT_AVAILABLE = 2, + MERR_DEVICE_NOT_AVAILABLE = 3, + MERR_ALREADY_OPEN = 4 + }; + + enum { +// PROP_TIMEDIV = 1, + PROP_OLD_ADLIB = 2, + PROP_CHANNEL_MASK = 3 + }; + + /** + * Open the midi driver. + * @return 0 if successful, otherwise an error code. + */ + virtual int open() = 0; + + /** Close the midi driver. */ + virtual void close() = 0; + + /** + * Output a packed midi command to the midi stream. + * The 'lowest' byte (i.e. b & 0xFF) is the status + * code, then come (if used) the first and second + * opcode. + */ + virtual void send(uint32 b) = 0; + + /** + * Output a midi command to the midi stream. Convenience wrapper + * around the usual 'packed' send method. + * + * Do NOT use this for sysEx transmission; instead, use the sysEx() + * method below. + */ + void send(byte status, byte firstOp, byte secondOp) { + send(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16)); + } + + /** Get or set a property. */ + virtual uint32 property(int prop, uint32 param) { return 0; } + + /** Retrieve a string representation of an error code. */ + static const char *getErrorName(int error_code); + + // HIGH-LEVEL SEMANTIC METHODS + virtual void setPitchBendRange(byte channel, uint range) { + send(0xB0 | channel, 101, 0); + send(0xB0 | channel, 100, 0); + send(0xB0 | channel, 6, range); + send(0xB0 | channel, 38, 0); + send(0xB0 | channel, 101, 127); + send(0xB0 | channel, 100, 127); + } + + /** + * Send a Roland MT-32 reset sysEx to the midi device. + */ + void sendMT32Reset(); + + /** + * Send a General MIDI reset sysEx to the midi device. + */ + void sendGMReset(); + + /** + * Transmit a sysEx to the midi device. + * + * The given msg MUST NOT contain the usual SysEx frame, i.e. + * do NOT include the leading 0xF0 and the trailing 0xF7. + * + * Furthermore, the maximal supported length of a SysEx + * is 264 bytes. Passing longer buffers can lead to + * undefined behavior (most likely, a crash). + */ + virtual void sysEx(const byte *msg, uint16 length) { } + + virtual void sysEx_customInstrument(byte channel, uint32 type, const byte *instr) { } + + virtual void metaEvent(byte type, byte *data, uint16 length) { } + + // Timing functions - MidiDriver now operates timers + virtual void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) = 0; + + /** The time in microseconds between invocations of the timer callback. */ + virtual uint32 getBaseTempo() = 0; + + // Channel allocation functions + virtual MidiChannel *allocateChannel() = 0; + virtual MidiChannel *getPercussionChannel() = 0; +}; + +class MidiChannel { +public: + virtual ~MidiChannel() {} + + virtual MidiDriver *device() = 0; + virtual byte getNumber() = 0; + virtual void release() = 0; + + virtual void send(uint32 b) = 0; // 4-bit channel portion is ignored + + // Regular messages + virtual void noteOff(byte note) = 0; + virtual void noteOn(byte note, byte velocity) = 0; + virtual void programChange(byte program) = 0; + virtual void pitchBend(int16 bend) = 0; // -0x2000 to +0x1FFF + + // Control Change messages + virtual void controlChange(byte control, byte value) = 0; + virtual void modulationWheel(byte value) { controlChange(1, value); } + virtual void volume(byte value) { controlChange(7, value); } + virtual void panPosition(byte value) { controlChange(10, value); } + virtual void pitchBendFactor(byte value) = 0; + virtual void detune(byte value) { controlChange(17, value); } + virtual void priority(byte value) { } + virtual void sustain(bool value) { controlChange(64, value ? 1 : 0); } + virtual void effectLevel(byte value) { controlChange(91, value); } + virtual void chorusLevel(byte value) { controlChange(93, value); } + virtual void allNotesOff() { controlChange(123, 0); } + + // SysEx messages + virtual void sysEx_customInstrument(uint32 type, const byte *instr) = 0; +}; + +#endif diff --git a/audio/midiparser.cpp b/audio/midiparser.cpp new file mode 100644 index 0000000000..e01b8a7fc6 --- /dev/null +++ b/audio/midiparser.cpp @@ -0,0 +1,467 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/midiparser.h" +#include "audio/mididrv.h" +#include "common/util.h" + +////////////////////////////////////////////////// +// +// MidiParser implementation +// +////////////////////////////////////////////////// + +MidiParser::MidiParser() : +_hanging_notes_count(0), +_driver(0), +_timer_rate(0x4A0000), +_ppqn(96), +_tempo(500000), +_psec_per_tick(5208), // 500000 / 96 +_autoLoop(false), +_smartJump(false), +_centerPitchWheelOnUnload(false), +_sendSustainOffOnNotesOff(false), +_num_tracks(0), +_active_track(255), +_abort_parse(0) { + memset(_active_notes, 0, sizeof(_active_notes)); + _next_event.start = NULL; + _next_event.delta = 0; + _next_event.event = 0; + _next_event.length = 0; +} + +void MidiParser::property(int prop, int value) { + switch (prop) { + case mpAutoLoop: + _autoLoop = (value != 0); + break; + case mpSmartJump: + _smartJump = (value != 0); + break; + case mpCenterPitchWheelOnUnload: + _centerPitchWheelOnUnload = (value != 0); + break; + case mpSendSustainOffOnNotesOff: + _sendSustainOffOnNotesOff = (value != 0); + break; + } +} + +void MidiParser::sendToDriver(uint32 b) { + _driver->send(b); +} + +void MidiParser::setTempo(uint32 tempo) { + _tempo = tempo; + if (_ppqn) + _psec_per_tick = (tempo + (_ppqn >> 2)) / _ppqn; +} + +// This is the conventional (i.e. SMF) variable length quantity +uint32 MidiParser::readVLQ(byte * &data) { + byte str; + uint32 value = 0; + int i; + + for (i = 0; i < 4; ++i) { + str = data[0]; + ++data; + value = (value << 7) | (str & 0x7F); + if (!(str & 0x80)) + break; + } + return value; +} + +void MidiParser::activeNote(byte channel, byte note, bool active) { + if (note >= 128 || channel >= 16) + return; + + if (active) + _active_notes[note] |= (1 << channel); + else + _active_notes[note] &= ~(1 << channel); + + // See if there are hanging notes that we can cancel + NoteTimer *ptr = _hanging_notes; + int i; + for (i = ARRAYSIZE(_hanging_notes); i; --i, ++ptr) { + if (ptr->channel == channel && ptr->note == note && ptr->time_left) { + ptr->time_left = 0; + --_hanging_notes_count; + break; + } + } +} + +void MidiParser::hangingNote(byte channel, byte note, uint32 time_left, bool recycle) { + NoteTimer *best = 0; + NoteTimer *ptr = _hanging_notes; + int i; + + if (_hanging_notes_count >= ARRAYSIZE(_hanging_notes)) { + warning("MidiParser::hangingNote(): Exceeded polyphony"); + return; + } + + for (i = ARRAYSIZE(_hanging_notes); i; --i, ++ptr) { + if (ptr->channel == channel && ptr->note == note) { + if (ptr->time_left && ptr->time_left < time_left && recycle) + return; + best = ptr; + if (ptr->time_left) { + if (recycle) + sendToDriver(0x80 | channel, note, 0); + --_hanging_notes_count; + } + break; + } else if (!best && ptr->time_left == 0) { + best = ptr; + } + } + + // Occassionally we might get a zero or negative note + // length, if the note should be turned on and off in + // the same iteration. For now just set it to 1 and + // we'll turn it off in the next cycle. + if (!time_left || time_left & 0x80000000) + time_left = 1; + + if (best) { + best->channel = channel; + best->note = note; + best->time_left = time_left; + ++_hanging_notes_count; + } else { + // We checked this up top. We should never get here! + warning("MidiParser::hangingNote(): Internal error"); + } +} + +void MidiParser::onTimer() { + uint32 end_time; + uint32 event_time; + + if (!_position._play_pos || !_driver) + return; + + _abort_parse = false; + end_time = _position._play_time + _timer_rate; + + // Scan our hanging notes for any + // that should be turned off. + if (_hanging_notes_count) { + NoteTimer *ptr = &_hanging_notes[0]; + int i; + for (i = ARRAYSIZE(_hanging_notes); i; --i, ++ptr) { + if (ptr->time_left) { + if (ptr->time_left <= _timer_rate) { + sendToDriver(0x80 | ptr->channel, ptr->note, 0); + ptr->time_left = 0; + --_hanging_notes_count; + } else { + ptr->time_left -= _timer_rate; + } + } + } + } + + while (!_abort_parse) { + EventInfo &info = _next_event; + + event_time = _position._last_event_time + info.delta * _psec_per_tick; + if (event_time > end_time) + break; + + // Process the next info. + _position._last_event_tick += info.delta; + if (info.event < 0x80) { + warning("Bad command or running status %02X", info.event); + _position._play_pos = 0; + return; + } + + if (info.event == 0xF0) { + // SysEx event + // Check for trailing 0xF7 -- if present, remove it. + if (info.ext.data[info.length-1] == 0xF7) + _driver->sysEx(info.ext.data, (uint16)info.length-1); + else + _driver->sysEx(info.ext.data, (uint16)info.length); + } else if (info.event == 0xFF) { + // META event + if (info.ext.type == 0x2F) { + // End of Track must be processed by us, + // as well as sending it to the output device. + if (_autoLoop) { + jumpToTick(0); + parseNextEvent(_next_event); + } else { + stopPlaying(); + _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length); + } + return; + } else if (info.ext.type == 0x51) { + if (info.length >= 3) { + setTempo(info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]); + } + } + _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length); + } else { + if (info.command() == 0x8) { + activeNote(info.channel(), info.basic.param1, false); + } else if (info.command() == 0x9) { + if (info.length > 0) + hangingNote(info.channel(), info.basic.param1, info.length * _psec_per_tick - (end_time - event_time)); + else + activeNote(info.channel(), info.basic.param1, true); + } + sendToDriver(info.event, info.basic.param1, info.basic.param2); + } + + + if (!_abort_parse) { + _position._last_event_time = event_time; + parseNextEvent(_next_event); + } + } + + if (!_abort_parse) { + _position._play_time = end_time; + _position._play_tick = (_position._play_time - _position._last_event_time) / _psec_per_tick + _position._last_event_tick; + } +} + +void MidiParser::allNotesOff() { + if (!_driver) + return; + + int i, j; + + // Turn off all active notes + for (i = 0; i < 128; ++i) { + for (j = 0; j < 16; ++j) { + if (_active_notes[i] & (1 << j)) { + sendToDriver(0x80 | j, i, 0); + } + } + } + + // Turn off all hanging notes + for (i = 0; i < ARRAYSIZE(_hanging_notes); i++) { + if (_hanging_notes[i].time_left) { + sendToDriver(0x80 | _hanging_notes[i].channel, _hanging_notes[i].note, 0); + _hanging_notes[i].time_left = 0; + } + } + _hanging_notes_count = 0; + + // To be sure, send an "All Note Off" event (but not all MIDI devices + // support this...). + + for (i = 0; i < 16; ++i) { + sendToDriver(0xB0 | i, 0x7b, 0); // All notes off + if (_sendSustainOffOnNotesOff) + sendToDriver(0xB0 | i, 0x40, 0); // Also send a sustain off event (bug #3116608) + } + + memset(_active_notes, 0, sizeof(_active_notes)); +} + +void MidiParser::resetTracking() { + _position.clear(); +} + +bool MidiParser::setTrack(int track) { + if (track < 0 || track >= _num_tracks) + return false; + // We allow restarting the track via setTrack when + // it isn't playing anymore. This allows us to reuse + // a MidiParser when a track has finished and will + // be restarted via setTrack by the client again. + // This isn't exactly how setTrack behaved before though, + // the old MidiParser code did not allow setTrack to be + // used to restart a track, which was already finished. + // + // TODO: Check if any engine has problem with this + // handling, if so we need to find a better way to handle + // track restarts. (KYRA relies on this working) + else if (track == _active_track && isPlaying()) + return true; + + if (_smartJump) + hangAllActiveNotes(); + else + allNotesOff(); + + resetTracking(); + memset(_active_notes, 0, sizeof(_active_notes)); + _active_track = track; + _position._play_pos = _tracks[track]; + parseNextEvent(_next_event); + return true; +} + +void MidiParser::stopPlaying() { + allNotesOff(); + resetTracking(); +} + +void MidiParser::hangAllActiveNotes() { + // Search for note off events until we have + // accounted for every active note. + uint16 temp_active[128]; + memcpy(temp_active, _active_notes, sizeof (temp_active)); + + uint32 advance_tick = _position._last_event_tick; + while (true) { + int i; + for (i = 0; i < 128; ++i) + if (temp_active[i] != 0) + break; + if (i == 128) + break; + parseNextEvent(_next_event); + advance_tick += _next_event.delta; + if (_next_event.command() == 0x8) { + if (temp_active[_next_event.basic.param1] & (1 << _next_event.channel())) { + hangingNote(_next_event.channel(), _next_event.basic.param1, (advance_tick - _position._last_event_tick) * _psec_per_tick, false); + temp_active[_next_event.basic.param1] &= ~ (1 << _next_event.channel()); + } + } else if (_next_event.event == 0xFF && _next_event.ext.type == 0x2F) { + // warning("MidiParser::hangAllActiveNotes(): Hit End of Track with active notes left"); + for (i = 0; i < 128; ++i) { + for (int j = 0; j < 16; ++j) { + if (temp_active[i] & (1 << j)) { + activeNote(j, i, false); + sendToDriver(0x80 | j, i, 0); + } + } + } + break; + } + } +} + +bool MidiParser::jumpToTick(uint32 tick, bool fireEvents, bool stopNotes, bool dontSendNoteOn) { + if (_active_track >= _num_tracks) + return false; + + Tracker currentPos(_position); + EventInfo currentEvent(_next_event); + + resetTracking(); + _position._play_pos = _tracks[_active_track]; + parseNextEvent(_next_event); + if (tick > 0) { + while (true) { + EventInfo &info = _next_event; + if (_position._last_event_tick + info.delta >= tick) { + _position._play_time += (tick - _position._last_event_tick) * _psec_per_tick; + _position._play_tick = tick; + break; + } + + _position._last_event_tick += info.delta; + _position._last_event_time += info.delta * _psec_per_tick; + _position._play_tick = _position._last_event_tick; + _position._play_time = _position._last_event_time; + + if (info.event == 0xFF) { + if (info.ext.type == 0x2F) { // End of track + _position = currentPos; + _next_event = currentEvent; + return false; + } else { + if (info.ext.type == 0x51 && info.length >= 3) // Tempo + setTempo(info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]); + if (fireEvents) + _driver->metaEvent(info.ext.type, info.ext.data, (uint16) info.length); + } + } else if (fireEvents) { + if (info.event == 0xF0) { + if (info.ext.data[info.length-1] == 0xF7) + _driver->sysEx(info.ext.data, (uint16)info.length-1); + else + _driver->sysEx(info.ext.data, (uint16)info.length); + } else { + // The note on sending code is used by the SCUMM engine. Other engine using this code + // (such as SCI) have issues with this, as all the notes sent can be heard when a song + // is fast-forwarded. Thus, if the engine requests it, don't send note on events. + if (info.command() == 0x9 && dontSendNoteOn) { + // Don't send note on; doing so creates a "warble" with some instruments on the MT-32. + // Refer to patch #3117577 + } else { + sendToDriver(info.event, info.basic.param1, info.basic.param2); + } + } + } + + parseNextEvent(_next_event); + } + } + + if (stopNotes) { + if (!_smartJump || !currentPos._play_pos) { + allNotesOff(); + } else { + EventInfo targetEvent(_next_event); + Tracker targetPosition(_position); + + _position = currentPos; + _next_event = currentEvent; + hangAllActiveNotes(); + + _next_event = targetEvent; + _position = targetPosition; + } + } + + _abort_parse = true; + return true; +} + +void MidiParser::unloadMusic() { + resetTracking(); + allNotesOff(); + _num_tracks = 0; + _active_track = 255; + _abort_parse = true; + + if (_centerPitchWheelOnUnload) { + // Center the pitch wheels in preparation for the next piece of + // music. It's not safe to do this from within allNotesOff(), + // and might not even be safe here, so we only do it if the + // client has explicitly asked for it. + + if (_driver) { + for (int i = 0; i < 16; ++i) { + sendToDriver(0xE0 | i, 0, 0x40); + } + } + } +} diff --git a/audio/midiparser.h b/audio/midiparser.h new file mode 100644 index 0000000000..0b18a19a5b --- /dev/null +++ b/audio/midiparser.h @@ -0,0 +1,404 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/// \brief Declarations related to the MidiParser class + +#ifndef SOUND_MIDIPARSER_H +#define SOUND_MIDIPARSER_H + +#include "common/scummsys.h" +#include "common/endian.h" + +class MidiParser; +class MidiDriver; + + + +////////////////////////////////////////////////// +// +// Support entities +// +////////////////////////////////////////////////// + +/** + * Maintains time and position state within a MIDI stream. + * A single Tracker struct is used by MidiParser to keep track + * of its current position in the MIDI stream. The Tracker + * struct, however, allows alternative locations to be cached. + * See MidiParser::jumpToTick() for an example of tracking + * multiple locations within a MIDI stream. NOTE: It is + * important to also maintain pre-parsed EventInfo data for + * each Tracker location. + */ +struct Tracker { + byte * _play_pos; ///< A pointer to the next event to be parsed + uint32 _play_time; ///< Current time in microseconds; may be in between event times + uint32 _play_tick; ///< Current MIDI tick; may be in between event ticks + uint32 _last_event_time; ///< The time, in microseconds, of the last event that was parsed + uint32 _last_event_tick; ///< The tick at which the last parsed event occurs + byte _running_status; ///< Cached MIDI command, for MIDI streams that rely on implied event codes + + Tracker() { clear(); } + + /// Copy constructor for each duplication of Tracker information. + Tracker(const Tracker ©) : + _play_pos(copy._play_pos), + _play_time(copy._play_time), + _play_tick(copy._play_tick), + _last_event_time(copy._last_event_time), + _last_event_tick(copy._last_event_tick), + _running_status(copy._running_status) + { } + + /// Clears all data; used by the constructor for initialization. + void clear() { + _play_pos = 0; + _play_time = 0; + _play_tick = 0; + _last_event_time = 0; + _last_event_tick = 0; + _running_status = 0; + } +}; + +/** + * Provides comprehensive information on the next event in the MIDI stream. + * An EventInfo struct is instantiated by format-specific implementations + * of MidiParser::parseNextEvent() each time another event is needed. + */ +struct EventInfo { + byte * start; ///< Position in the MIDI stream where the event starts. + ///< For delta-based MIDI streams (e.g. SMF and XMIDI), this points to the delta. + uint32 delta; ///< The number of ticks after the previous event that this event should occur. + byte event; ///< Upper 4 bits are the command code, lower 4 bits are the MIDI channel. + ///< For META, event == 0xFF. For SysEx, event == 0xF0. + union { + struct { + byte param1; ///< The first parameter in a simple MIDI message. + byte param2; ///< The second parameter in a simple MIDI message. + } basic; + struct { + byte type; ///< For META events, this indicates the META type. + byte * data; ///< For META and SysEx events, this points to the start of the data. + } ext; + }; + uint32 length; ///< For META and SysEx blocks, this indicates the length of the data. + ///< For Note On events, a non-zero value indicates that no Note Off event + ///< will occur, and the MidiParser will have to generate one itself. + ///< For all other events, this value should always be zero. + + byte channel() { return event & 0x0F; } ///< Separates the MIDI channel from the event. + byte command() { return event >> 4; } ///< Separates the command code from the event. +}; + +/** + * Provides expiration tracking for hanging notes. + * Hanging notes are used when a MIDI format does not include explicit Note Off + * events, or when "Smart Jump" is enabled so that active notes are intelligently + * expired when a jump occurs. The NoteTimer struct keeps track of how much + * longer a note should remain active before being turned off. + */ +struct NoteTimer { + byte channel; ///< The MIDI channel on which the note was played + byte note; ///< The note number for the active note + uint32 time_left; ///< The time, in microseconds, remaining before the note should be turned off + NoteTimer() : channel(0), note(0), time_left(0) {} +}; + + + + +////////////////////////////////////////////////// +// +// MidiParser declaration +// +////////////////////////////////////////////////// + +/** + * A framework and common functionality for parsing event-based music streams. + * The MidiParser provides a framework in which to load, + * parse and traverse event-based music data. Note the + * avoidance of the phrase "MIDI data." Despite its name, + * MidiParser derivatives can be used to manage a wide + * variety of event-based music formats. It is, however, + * based on the premise that the format in question can + * be played in the form of specification MIDI events. + * + * In order to use MidiParser to parse your music format, + * follow these steps: + * + * STEP 1: Write a MidiParser derivative. + * The MidiParser base class provides functionality + * considered common to the task of parsing event-based + * music. In order to parse a particular format, create + * a derived class that implements, at minimum, the + * following format-specific methods: + * - loadMusic + * - parseNextEvent + * + * In addition to the above functions, the derived class + * may also override the default MidiParser behavior for + * the following methods: + * - resetTracking + * - allNotesOff + * - unloadMusic + * - property + * - getTick + * + * Please see the documentation for these individual + * functions for more information on their use. + * + * The naming convention for classes derived from + * MidiParser is MidiParser_XXX, where "XXX" is some + * short designator for the format the class will + * support. For instance, the MidiParser derivative + * for parsing the Standard MIDI File format is + * MidiParser_SMF. + * + * STEP 2: Create an object of your derived class. + * Each MidiParser object can parse at most one (1) song + * at a time. However, a MidiParser object can be reused + * to play another song once it is no longer needed to + * play whatever it was playing. In other words, MidiParser + * objects do not have to be destroyed and recreated from + * one song to the next. + * + * STEP 3: Specify a MidiDriver to send events to. + * MidiParser works by sending MIDI and meta events to a + * MidiDriver. In the simplest configuration, you can plug + * a single MidiParser directly into the output MidiDriver + * being used. However, you can only plug in one at a time; + * otherwise channel conflicts will occur. Furthermore, + * meta events that may be needed to interactively control + * music flow cannot be handled because they are being + * sent directly to the output device. + * + * If you need more control over the MidiParser while it's + * playing, you can create your own "pseudo-MidiDriver" and + * place it in between your MidiParser and the output + * MidiDriver. The MidiParser will send events to your + * pseudo-MidiDriver, which in turn must send them to the + * output MidiDriver (or do whatever special handling is + * required). + * + * To specify the MidiDriver to send music output to, + * use the MidiParser::setMidiDriver method. + * + * STEP 4: Specify the onTimer call rate. + * MidiParser bases the timing of its parsing on an external + * clock. Every time MidiParser::onTimer is called, a bit + * more music is parsed. You must specify how many + * microseconds will occur between each call to onTimer, + * in order to ensure an accurate music tempo. + * + * To set the onTimer call rate, in microseconds, + * use the MidiParser::setTimerRate method. The onTimer + * call rate will typically match the timer rate for + * the output MidiDriver used. This rate can be obtained + * by calling MidiDriver::getBaseTempo. + * + * STEP 5: Load the music. + * MidiParser requires that the music data already be loaded + * into memory. The client code is responsible for memory + * management on this block of memory. That means that the + * client code must ensure that the data remain in memory + * while the MidiParser is using it, and properly freed + * after it is no longer needed. Some MidiParser variants may + * require internal buffers as well; memory management for those + * buffers is the responsibility of the MidiParser object. + * + * To load the music into the MidiParser, use the + * MidiParser::loadMusic method, specifying a memory pointer + * to the music data and the size of the data. (NOTE: Some + * MidiParser variants don't require a size, and 0 is fine. + * However, when writing client code to use MidiParser, it is + * best to assume that a valid size will be required. + * + * Convention requires that each implementation of + * MidiParser::loadMusic automatically set up default tempo + * and current track. This effectively means that the + * MidiParser will start playing as soon as timer events + * start coming in. + * + * STEP 6: Activate a timer source for the MidiParser. + * The easiest timer source to use is the timer of the + * output MidiDriver. You can attach the MidiDriver's + * timer output directly to a MidiParser by calling + * MidiDriver::setTimerCallback. In this case, the timer_proc + * will be the static method MidiParser::timerCallback, + * and timer_param will be a pointer to your MidiParser object. + * + * This configuration only allows one MidiParser to be driven + * by the MidiDriver at a time. To drive more MidiDrivers, you + * will need to create a "pseudo-MidiDriver" as described earlier, + * In such a configuration, the pseudo-MidiDriver should be set + * as the timer recipient in MidiDriver::setTimerCallback, and + * could then call MidiParser::onTimer for each MidiParser object. + * + * STEP 7: Music shall begin to play! + * Congratulations! At this point everything should be hooked up + * and the MidiParser should generate music. Note that there is + * no way to "stop" the MidiParser. You can "pause" the MidiParser + * simply by not sending timer events to it, or you can call + * MidiParser::unloadMusic to permanently stop the music. (This + * method resets everything and detaches the MidiParser from the + * memory block containing the music data.) + */ +class MidiParser { +protected: + uint16 _active_notes[128]; ///< Each uint16 is a bit mask for channels that have that note on. + NoteTimer _hanging_notes[32]; ///< Maintains expiration info for up to 32 notes. + ///< Used for "Smart Jump" and MIDI formats that do not include explicit Note Off events. + byte _hanging_notes_count; ///< Count of hanging notes, used to optimize expiration. + + MidiDriver *_driver; ///< The device to which all events will be transmitted. + uint32 _timer_rate; ///< The time in microseconds between onTimer() calls. Obtained from the MidiDriver. + uint32 _ppqn; ///< Pulses Per Quarter Note. (We refer to "pulses" as "ticks".) + uint32 _tempo; ///< Microseconds per quarter note. + uint32 _psec_per_tick; ///< Microseconds per tick (_tempo / _ppqn). + bool _autoLoop; ///< For lightweight clients that don't provide their own flow control. + bool _smartJump; ///< Support smart expiration of hanging notes when jumping + bool _centerPitchWheelOnUnload; ///< Center the pitch wheels when unloading a song + bool _sendSustainOffOnNotesOff; ///< Send a sustain off on a notes off event, stopping hanging notes + byte *_tracks[120]; ///< Multi-track MIDI formats are supported, up to 120 tracks. + byte _num_tracks; ///< Count of total tracks for multi-track MIDI formats. 1 for single-track formats. + byte _active_track; ///< Keeps track of the currently active track, in multi-track formats. + + Tracker _position; ///< The current time/position in the active track. + EventInfo _next_event; ///< The next event to transmit. Events are preparsed + ///< so each event is parsed only once; this permits + ///< simulated events in certain formats. + bool _abort_parse; ///< If a jump or other operation interrupts parsing, flag to abort. + +protected: + static uint32 readVLQ(byte * &data); + virtual void resetTracking(); + virtual void allNotesOff(); + virtual void parseNextEvent(EventInfo &info) = 0; + + void activeNote(byte channel, byte note, bool active); + void hangingNote(byte channel, byte note, uint32 ticks_left, bool recycle = true); + void hangAllActiveNotes(); + + virtual void sendToDriver(uint32 b); + void sendToDriver(byte status, byte firstOp, byte secondOp) { + sendToDriver(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16)); + } + + /** + * Platform independent BE uint32 read-and-advance. + * This helper function reads Big Endian 32-bit numbers + * from a memory pointer, at the same time advancing + * the pointer. + */ + uint32 read4high(byte * &data) { + uint32 val = READ_BE_UINT32(data); + data += 4; + return val; + } + + /** + * Platform independent LE uint16 read-and-advance. + * This helper function reads Little Endian 16-bit numbers + * from a memory pointer, at the same time advancing + * the pointer. + */ + uint16 read2low(byte * &data) { + uint16 val = READ_LE_UINT16(data); + data += 2; + return val; + } + +public: + /** + * Configuration options for MidiParser + * The following options can be set to modify MidiParser's + * behavior. + */ + enum { + /** + * Events containing a pitch bend command should be treated as + * single-byte padding before the real event. This allows the + * MidiParser to work with some malformed SMF files from Simon 1/2. + */ + mpMalformedPitchBends = 1, + + /** + * Sets auto-looping, which can be used by lightweight clients + * that don't provide their own flow control. + */ + mpAutoLoop = 2, + + /** + * Sets smart jumping, which intelligently expires notes that are + * active when a jump is made, rather than just cutting them off. + */ + mpSmartJump = 3, + + /** + * Center the pitch wheels when unloading music in preparation + * for the next piece of music. + */ + mpCenterPitchWheelOnUnload = 4, + + /** + * Sends a sustain off event when a notes off event is triggered. + * Stops hanging notes. + */ + mpSendSustainOffOnNotesOff = 5 + }; + +public: + typedef void (*XMidiCallbackProc)(byte eventData, void *refCon); + + MidiParser(); + virtual ~MidiParser() { allNotesOff(); } + + virtual bool loadMusic(byte *data, uint32 size) = 0; + virtual void unloadMusic(); + virtual void property(int prop, int value); + + void setMidiDriver(MidiDriver *driver) { _driver = driver; } + void setTimerRate(uint32 rate) { _timer_rate = rate; } + void setTempo(uint32 tempo); + void onTimer(); + + bool isPlaying() const { return (_position._play_pos != 0); } + void stopPlaying(); + + bool setTrack(int track); + bool jumpToTick(uint32 tick, bool fireEvents = false, bool stopNotes = true, bool dontSendNoteOn = false); + + uint32 getPPQN() { return _ppqn; } + virtual uint32 getTick() { return _position._play_tick; } + + static void defaultXMidiCallback(byte eventData, void *refCon); + + static MidiParser *createParser_SMF(); + static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0); + static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); } +}; + +#endif diff --git a/audio/midiparser_smf.cpp b/audio/midiparser_smf.cpp new file mode 100644 index 0000000000..9e4e8ed293 --- /dev/null +++ b/audio/midiparser_smf.cpp @@ -0,0 +1,384 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/midiparser.h" +#include "audio/mididrv.h" +#include "common/util.h" + +/** + * The Standard MIDI File version of MidiParser. + */ +class MidiParser_SMF : public MidiParser { +protected: + byte *_buffer; + bool _malformedPitchBends; + +protected: + void compressToType0(); + void parseNextEvent(EventInfo &info); + +public: + MidiParser_SMF() : _buffer(0), _malformedPitchBends(false) {} + ~MidiParser_SMF(); + + bool loadMusic(byte *data, uint32 size); + void property(int property, int value); +}; + + +static const byte command_lengths[8] = { 3, 3, 3, 3, 2, 2, 3, 0 }; +static const byte special_lengths[16] = { 0, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }; + +MidiParser_SMF::~MidiParser_SMF() { + free(_buffer); +} + +void MidiParser_SMF::property(int prop, int value) { + switch (prop) { + case mpMalformedPitchBends: + _malformedPitchBends = (value > 0); + default: + MidiParser::property(prop, value); + } +} + +void MidiParser_SMF::parseNextEvent(EventInfo &info) { + info.start = _position._play_pos; + info.delta = readVLQ(_position._play_pos); + + // Process the next info. If mpMalformedPitchBends + // was set, we must skip over any pitch bend events + // because they are from Simon games and are not + // real pitch bend events, they're just two-byte + // prefixes before the real info. + do { + if ((_position._play_pos[0] & 0xF0) >= 0x80) + info.event = *(_position._play_pos++); + else + info.event = _position._running_status; + } while (_malformedPitchBends && (info.event & 0xF0) == 0xE0 && _position._play_pos++); + if (info.event < 0x80) + return; + + _position._running_status = info.event; + switch (info.command()) { + case 0x9: // Note On + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = *(_position._play_pos++); + if (info.basic.param2 == 0) + info.event = info.channel() | 0x80; + info.length = 0; + break; + + case 0xC: + case 0xD: + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = 0; + break; + + case 0x8: + case 0xA: + case 0xB: + case 0xE: + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = *(_position._play_pos++); + info.length = 0; + break; + + case 0xF: // System Common, Meta or SysEx event + switch (info.event & 0x0F) { + case 0x2: // Song Position Pointer + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = *(_position._play_pos++); + break; + + case 0x3: // Song Select + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = 0; + break; + + case 0x6: + case 0x8: + case 0xA: + case 0xB: + case 0xC: + case 0xE: + info.basic.param1 = info.basic.param2 = 0; + break; + + case 0x0: // SysEx + info.length = readVLQ(_position._play_pos); + info.ext.data = _position._play_pos; + _position._play_pos += info.length; + break; + + case 0xF: // META event + info.ext.type = *(_position._play_pos++); + info.length = readVLQ(_position._play_pos); + info.ext.data = _position._play_pos; + _position._play_pos += info.length; + break; + + default: + warning("MidiParser_SMF::parseNextEvent: Unsupported event code %x", info.event); + } + } +} + +bool MidiParser_SMF::loadMusic(byte *data, uint32 size) { + uint32 len; + byte midi_type; + uint32 total_size; + bool isGMF; + + unloadMusic(); + byte *pos = data; + isGMF = false; + + if (!memcmp(pos, "RIFF", 4)) { + // Skip the outer RIFF header. + pos += 8; + } + + if (!memcmp(pos, "MThd", 4)) { + // SMF with MTHd information. + pos += 4; + len = read4high(pos); + if (len != 6) { + warning("MThd length 6 expected but found %d", (int)len); + return false; + } + + // Verify that this MIDI either is a Type 2 + // or has only 1 track. We do not support + // multitrack Type 1 files. + _num_tracks = pos[2] << 8 | pos[3]; + midi_type = pos[1]; + if (midi_type > 2 /*|| (midi_type < 2 && _num_tracks > 1)*/) { + warning("No support for a Type %d MIDI with %d tracks", (int)midi_type, (int)_num_tracks); + return false; + } + _ppqn = pos[4] << 8 | pos[5]; + pos += len; + } else if (!memcmp(pos, "GMF\x1", 4)) { + // Older GMD/MUS file with no header info. + // Assume 1 track, 192 PPQN, and no MTrk headers. + isGMF = true; + midi_type = 0; + _num_tracks = 1; + _ppqn = 192; + pos += 7; // 'GMD\x1' + 3 bytes of useless (translate: unknown) information + } else { + warning("Expected MThd or GMD header but found '%c%c%c%c' instead", pos[0], pos[1], pos[2], pos[3]); + return false; + } + + // Now we identify and store the location for each track. + if (_num_tracks > ARRAYSIZE(_tracks)) { + warning("Can only handle %d tracks but was handed %d", (int)ARRAYSIZE(_tracks), (int)_num_tracks); + return false; + } + + total_size = 0; + int tracks_read = 0; + while (tracks_read < _num_tracks) { + if (memcmp(pos, "MTrk", 4) && !isGMF) { + warning("Position: %p ('%c')", pos, *pos); + warning("Hit invalid block '%c%c%c%c' while scanning for track locations", pos[0], pos[1], pos[2], pos[3]); + return false; + } + + // If needed, skip the MTrk and length bytes + _tracks[tracks_read] = pos + (isGMF ? 0 : 8); + if (!isGMF) { + pos += 4; + len = read4high(pos); + total_size += len; + pos += len; + } else { + // An SMF End of Track meta event must be placed + // at the end of the stream. + data[size++] = 0xFF; + data[size++] = 0x2F; + data[size++] = 0x00; + data[size++] = 0x00; + } + ++tracks_read; + } + + // If this is a Type 1 MIDI, we need to now compress + // our tracks down into a single Type 0 track. + free(_buffer); + _buffer = 0; + + if (midi_type == 1) { + // FIXME: Doubled the buffer size to prevent crashes with the + // Inherit the Earth MIDIs. Jamieson630 said something about a + // better fix, but this will have to do in the meantime. + _buffer = (byte *)malloc(size * 2); + compressToType0(); + _num_tracks = 1; + _tracks[0] = _buffer; + } + + // Note that we assume the original data passed in + // will persist beyond this call, i.e. we do NOT + // copy the data to our own buffer. Take warning.... + resetTracking(); + setTempo(500000); + setTrack(0); + return true; +} + +void MidiParser_SMF::compressToType0() { + // We assume that _buffer has been allocated + // to sufficient size for this operation. + + // using 0xFF since it could write track_pos[0 to _num_tracks] here + // this would cause some illegal writes and could lead to segfaults + // (it crashed for some midis for me, they're not used in any game + // scummvm supports though). *Maybe* handle this in another way, + // it's at the moment only to be sure, that nothing goes wrong. + byte *track_pos[0xFF]; + byte running_status[0xFF]; + uint32 track_timer[0xFF]; + uint32 delta; + int i; + + for (i = 0; i < _num_tracks; ++i) { + running_status[i] = 0; + track_pos[i] = _tracks[i]; + track_timer[i] = readVLQ(track_pos[i]); + running_status[i] = 0; + } + + int best_i; + uint32 length; + byte *output = _buffer; + byte *pos, *pos2; + byte event; + uint32 copy_bytes; + bool write; + byte active_tracks = (byte)_num_tracks; + + while (active_tracks) { + write = true; + best_i = 255; + for (i = 0; i < _num_tracks; ++i) { + if (track_pos[i] && (best_i == 255 || track_timer[i] < track_timer[best_i])) + best_i = i; + } + if (best_i == 255) { + warning("Premature end of tracks"); + break; + } + + // Initial VLQ delta computation + delta = 0; + length = track_timer[best_i]; + for (i = 0; length; ++i) { + delta = (delta << 8) | (length & 0x7F) | (i ? 0x80 : 0); + length >>= 7; + } + + // Process MIDI event. + bool implicitEvent = false; + copy_bytes = 0; + pos = track_pos[best_i]; + do { + event = *(pos++); + if (event < 0x80) { + event = running_status[best_i]; + implicitEvent = true; + } + } while (_malformedPitchBends && (event & 0xF0) == 0xE0 && pos++); + running_status[best_i] = event; + + if (command_lengths[(event >> 4) - 8] > 0) { + copy_bytes = command_lengths[(event >> 4) - 8]; + } else if (special_lengths[(event & 0x0F)] > 0) { + copy_bytes = special_lengths[(event & 0x0F)]; + } else if (event == 0xF0) { + // SysEx + pos2 = pos; + length = readVLQ(pos); + copy_bytes = 1 + (pos - pos2) + length; + } else if (event == 0xFF) { + // META + event = *(pos++); + if (event == 0x2F && active_tracks > 1) { + track_pos[best_i] = 0; + write = false; + } else { + pos2 = pos; + length = readVLQ(pos); + copy_bytes = 2 + (pos - pos2) + length; + } + if (event == 0x2F) + --active_tracks; + } else { + warning("Bad MIDI command %02X", (int)event); + track_pos[best_i] = 0; + } + + // Update all tracks' deltas + if (write) { + for (i = 0; i < _num_tracks; ++i) { + if (track_pos[i] && i != best_i) + track_timer[i] -= track_timer[best_i]; + } + } + + if (track_pos[best_i]) { + if (write) { + track_timer[best_i] = 0; + + // Write VLQ delta + while (delta & 0x80) { + *output++ = (byte)(delta & 0xFF); + delta >>= 8; + } + *output++ = (byte)(delta & 0xFF); + + // Write MIDI data + if (!implicitEvent) + ++track_pos[best_i]; + --copy_bytes; + *output++ = running_status[best_i]; + memcpy(output, track_pos[best_i], copy_bytes); + output += copy_bytes; + } + + // Fetch new VLQ delta for winning track + track_pos[best_i] += copy_bytes; + if (active_tracks) + track_timer[best_i] += readVLQ(track_pos[best_i]); + } + } + + *output++ = 0x00; +} + +MidiParser *MidiParser::createParser_SMF() { return new MidiParser_SMF; } diff --git a/audio/midiparser_xmidi.cpp b/audio/midiparser_xmidi.cpp new file mode 100644 index 0000000000..edc7c7a943 --- /dev/null +++ b/audio/midiparser_xmidi.cpp @@ -0,0 +1,375 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/midiparser.h" +#include "audio/mididrv.h" +#include "common/util.h" + +/** + * The XMIDI version of MidiParser. + * + * Much of this code is adapted from the XMIDI implementation from the exult + * project. + */ +class MidiParser_XMIDI : public MidiParser { +protected: + NoteTimer _notes_cache[32]; + uint32 _inserted_delta; // Track simulated deltas for note-off events + + struct Loop { + byte *pos; + byte repeat; + }; + + Loop _loop[4]; + int _loopCount; + + XMidiCallbackProc _callbackProc; + void *_callbackData; + +protected: + uint32 readVLQ2(byte * &data); + void resetTracking(); + void parseNextEvent(EventInfo &info); + +public: + MidiParser_XMIDI(XMidiCallbackProc proc, void *data) : _inserted_delta(0), _callbackProc(proc), _callbackData(data) {} + ~MidiParser_XMIDI() { } + + bool loadMusic(byte *data, uint32 size); +}; + + +// This is a special XMIDI variable length quantity +uint32 MidiParser_XMIDI::readVLQ2(byte * &pos) { + uint32 value = 0; + while (!(pos[0] & 0x80)) { + value += *pos++; + } + return value; +} + +void MidiParser_XMIDI::parseNextEvent(EventInfo &info) { + info.start = _position._play_pos; + info.delta = readVLQ2(_position._play_pos) - _inserted_delta; + + // Process the next event. + _inserted_delta = 0; + info.event = *(_position._play_pos++); + switch (info.event >> 4) { + case 0x9: // Note On + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = *(_position._play_pos++); + info.length = readVLQ(_position._play_pos); + if (info.basic.param2 == 0) { + info.event = info.channel() | 0x80; + info.length = 0; + } + break; + + case 0xC: + case 0xD: + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = 0; + break; + + case 0x8: + case 0xA: + case 0xE: + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = *(_position._play_pos++); + break; + + case 0xB: + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = *(_position._play_pos++); + + // This isn't a full XMIDI implementation, but it should + // hopefully be "good enough" for most things. + + switch (info.basic.param1) { + // Simplified XMIDI looping. + case 0x74: { // XMIDI_CONTROLLER_FOR_LOOP + byte *pos = _position._play_pos; + if (_loopCount < ARRAYSIZE(_loop) - 1) + _loopCount++; + else + warning("XMIDI: Exceeding maximum loop count %d", ARRAYSIZE(_loop)); + + _loop[_loopCount].pos = pos; + _loop[_loopCount].repeat = info.basic.param2; + break; + } + + case 0x75: // XMIDI_CONTORLLER_NEXT_BREAK + if (_loopCount >= 0) { + if (info.basic.param2 < 64) { + // End the current loop. + _loopCount--; + } else { + // Repeat 0 means "loop forever". + if (_loop[_loopCount].repeat) { + if (--_loop[_loopCount].repeat == 0) + _loopCount--; + else + _position._play_pos = _loop[_loopCount].pos; + } else { + _position._play_pos = _loop[_loopCount].pos; + } + } + } + break; + + case 0x77: // XMIDI_CONTROLLER_CALLBACK_TRIG + if (_callbackProc) + _callbackProc(info.basic.param2, _callbackData); + break; + + case 0x6e: // XMIDI_CONTROLLER_CHAN_LOCK + case 0x6f: // XMIDI_CONTROLLER_CHAN_LOCK_PROT + case 0x70: // XMIDI_CONTROLLER_VOICE_PROT + case 0x71: // XMIDI_CONTROLLER_TIMBRE_PROT + case 0x72: // XMIDI_CONTROLLER_BANK_CHANGE + case 0x73: // XMIDI_CONTROLLER_IND_CTRL_PREFIX + case 0x76: // XMIDI_CONTROLLER_CLEAR_BB_COUNT + case 0x78: // XMIDI_CONTROLLER_SEQ_BRANCH_INDEX + default: + if (info.basic.param1 >= 0x6e && info.basic.param1 <= 0x78) { + warning("Unsupported XMIDI controller %d (0x%2x)", + info.basic.param1, info.basic.param1); + } + } + + // Should we really keep passing the XMIDI controller events to + // the MIDI driver, or should we turn them into some kind of + // NOP events? (Dummy meta events, perhaps?) Ah well, it has + // worked so far, so it shouldn't cause any damage... + + break; + + case 0xF: // Meta or SysEx event + switch (info.event & 0x0F) { + case 0x2: // Song Position Pointer + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = *(_position._play_pos++); + break; + + case 0x3: // Song Select + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = 0; + break; + + case 0x6: + case 0x8: + case 0xA: + case 0xB: + case 0xC: + case 0xE: + info.basic.param1 = info.basic.param2 = 0; + break; + + case 0x0: // SysEx + info.length = readVLQ(_position._play_pos); + info.ext.data = _position._play_pos; + _position._play_pos += info.length; + break; + + case 0xF: // META event + info.ext.type = *(_position._play_pos++); + info.length = readVLQ(_position._play_pos); + info.ext.data = _position._play_pos; + _position._play_pos += info.length; + if (info.ext.type == 0x51 && info.length == 3) { + // Tempo event. We want to make these constant 500,000. + info.ext.data[0] = 0x07; + info.ext.data[1] = 0xA1; + info.ext.data[2] = 0x20; + } + break; + + default: + warning("MidiParser_XMIDI::parseNextEvent: Unsupported event code %x", info.event); + } + } +} + +bool MidiParser_XMIDI::loadMusic(byte *data, uint32 size) { + uint32 i = 0; + byte *start; + uint32 len; + uint32 chunk_len; + char buf[32]; + + _loopCount = -1; + + unloadMusic(); + byte *pos = data; + + if (!memcmp(pos, "FORM", 4)) { + pos += 4; + + // Read length of + len = read4high(pos); + start = pos; + + // XDIRless XMIDI, we can handle them here. + if (!memcmp(pos, "XMID", 4)) { + warning("XMIDI doesn't have XDIR"); + pos += 4; + _num_tracks = 1; + } else if (memcmp(pos, "XDIR", 4)) { + // Not an XMIDI that we recognise + warning("Expected 'XDIR' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]); + return false; + } else { + // Seems Valid + pos += 4; + _num_tracks = 0; + + for (i = 4; i < len; i++) { + // Read 4 bytes of type + memcpy(buf, pos, 4); + pos += 4; + + // Read length of chunk + chunk_len = read4high(pos); + + // Add eight bytes + i += 8; + + if (memcmp(buf, "INFO", 4)) { + // Must align + pos += (chunk_len + 1) & ~1; + i += (chunk_len + 1) & ~1; + continue; + } + + // Must be at least 2 bytes long + if (chunk_len < 2) { + warning("Invalid chunk length %d for 'INFO' block", (int)chunk_len); + return false; + } + + _num_tracks = (byte)read2low(pos); + + if (chunk_len > 2) { + warning("Chunk length %d is greater than 2", (int)chunk_len); + pos += chunk_len - 2; + } + break; + } + + // Didn't get to fill the header + if (_num_tracks == 0) { + warning("Didn't find a valid track count"); + return false; + } + + // Ok now to start part 2 + // Goto the right place + pos = start + ((len + 1) & ~1); + + if (memcmp(pos, "CAT ", 4)) { + // Not an XMID + warning("Expected 'CAT ' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]); + return false; + } + pos += 4; + + // Now read length of this track + len = read4high(pos); + + if (memcmp(pos, "XMID", 4)) { + // Not an XMID + warning("Expected 'XMID' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]); + return false; + } + pos += 4; + + } + + // Ok it's an XMIDI. + // We're going to identify and store the location for each track. + if (_num_tracks > ARRAYSIZE(_tracks)) { + warning("Can only handle %d tracks but was handed %d", (int)ARRAYSIZE(_tracks), (int)_num_tracks); + return false; + } + + int tracks_read = 0; + while (tracks_read < _num_tracks) { + if (!memcmp(pos, "FORM", 4)) { + // Skip this plus the 4 bytes after it. + pos += 8; + } else if (!memcmp(pos, "XMID", 4)) { + // Skip this. + pos += 4; + } else if (!memcmp(pos, "TIMB", 4)) { + // Custom timbres? + // We don't support them. + // Read the length, skip it, and hope there was nothing there. + pos += 4; + len = read4high(pos); + pos += (len + 1) & ~1; + } else if (!memcmp(pos, "EVNT", 4)) { + // Ahh! What we're looking for at last. + _tracks[tracks_read] = pos + 8; // Skip the EVNT and length bytes + pos += 4; + len = read4high(pos); + pos += (len + 1) & ~1; + ++tracks_read; + } else { + warning("Hit invalid block '%c%c%c%c' while scanning for track locations", pos[0], pos[1], pos[2], pos[3]); + return false; + } + } + + // If we got this far, we successfully established + // the locations for each of our tracks. + // Note that we assume the original data passed in + // will persist beyond this call, i.e. we do NOT + // copy the data to our own buffer. Take warning.... + _ppqn = 60; + resetTracking(); + setTempo(500000); + _inserted_delta = 0; + setTrack(0); + return true; + } + + return false; +} + +void MidiParser_XMIDI::resetTracking() { + MidiParser::resetTracking(); + _inserted_delta = 0; +} + +void MidiParser::defaultXMidiCallback(byte eventData, void *data) { + warning("MidiParser: defaultXMidiCallback(%d)", eventData); +} + +MidiParser *MidiParser::createParser_XMIDI(XMidiCallbackProc proc, void *data) { + return new MidiParser_XMIDI(proc, data); +} diff --git a/audio/mixer.cpp b/audio/mixer.cpp new file mode 100644 index 0000000000..c2271b1059 --- /dev/null +++ b/audio/mixer.cpp @@ -0,0 +1,556 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/util.h" +#include "common/system.h" + +#include "audio/mixer_intern.h" +#include "audio/rate.h" +#include "audio/audiostream.h" +#include "audio/timestamp.h" + + +namespace Audio { + +#pragma mark - +#pragma mark --- Channel classes --- +#pragma mark - + + +/** + * Channel used by the default Mixer implementation. + */ +class Channel { +public: + Channel(Mixer *mixer, Mixer::SoundType type, AudioStream *stream, DisposeAfterUse::Flag autofreeStream, bool reverseStereo, int id, bool permanent); + ~Channel(); + + /** + * Mixes the channel's samples into the given buffer. + * + * @param data buffer where to mix the data + * @param len number of sample *pairs*. So a value of + * 10 means that the buffer contains twice 10 sample, each + * 16 bits, for a total of 40 bytes. + */ + void mix(int16 *data, uint len); + + /** + * Queries whether the channel is still playing or not. + */ + bool isFinished() const { return _stream->endOfStream(); } + + /** + * Queries whether the channel is a permanent channel. + * A permanent channel is not affected by a Mixer::stopAll + * call. + */ + bool isPermanent() const { return _permanent; } + + /** + * Returns the id of the channel. + */ + int getId() const { return _id; } + + /** + * Pauses or unpaused the channel in a recursive fashion. + * + * @param paused true, when the channel should be paused. + * false when it should be unpaused. + */ + void pause(bool paused); + + /** + * Queries whether the channel is currently paused. + */ + bool isPaused() const { return (_pauseLevel != 0); } + + /** + * Sets the channel's own volume. + * + * @param volume new volume + */ + void setVolume(const byte volume); + + /** + * Sets the channel's balance setting. + * + * @param balance new balance + */ + void setBalance(const int8 balance); + + /** + * Notifies the channel that the global sound type + * volume settings changed. + */ + void notifyGlobalVolChange() { updateChannelVolumes(); } + + /** + * Queries how long the channel has been playing. + */ + Timestamp getElapsedTime(); + + /** + * Queries the channel's sound type. + */ + Mixer::SoundType getType() const { return _type; } + + /** + * Sets the channel's sound handle. + * + * @param handle new handle + */ + void setHandle(const SoundHandle handle) { _handle = handle; } + + /** + * Queries the channel's sound handle. + */ + SoundHandle getHandle() const { return _handle; } + +private: + const Mixer::SoundType _type; + SoundHandle _handle; + bool _permanent; + int _pauseLevel; + int _id; + + byte _volume; + int8 _balance; + + void updateChannelVolumes(); + st_volume_t _volL, _volR; + + Mixer *_mixer; + + uint32 _samplesConsumed; + uint32 _samplesDecoded; + uint32 _mixerTimeStamp; + uint32 _pauseStartTime; + uint32 _pauseTime; + + DisposeAfterUse::Flag _autofreeStream; + RateConverter *_converter; + AudioStream *_stream; +}; + +#pragma mark - +#pragma mark --- Mixer --- +#pragma mark - + + +MixerImpl::MixerImpl(OSystem *system, uint sampleRate) + : _syst(system), _sampleRate(sampleRate), _mixerReady(false), _handleSeed(0) { + + assert(sampleRate > 0); + + int i; + + for (i = 0; i < ARRAYSIZE(_volumeForSoundType); i++) + _volumeForSoundType[i] = kMaxMixerVolume; + + for (i = 0; i != NUM_CHANNELS; i++) + _channels[i] = 0; +} + +MixerImpl::~MixerImpl() { + for (int i = 0; i != NUM_CHANNELS; i++) + delete _channels[i]; +} + +void MixerImpl::setReady(bool ready) { + _mixerReady = ready; +} + +uint MixerImpl::getOutputRate() const { + return _sampleRate; +} + +void MixerImpl::insertChannel(SoundHandle *handle, Channel *chan) { + int index = -1; + for (int i = 0; i != NUM_CHANNELS; i++) { + if (_channels[i] == 0) { + index = i; + break; + } + } + if (index == -1) { + warning("MixerImpl::out of mixer slots"); + delete chan; + return; + } + + _channels[index] = chan; + + SoundHandle chanHandle; + chanHandle._val = index + (_handleSeed * NUM_CHANNELS); + + chan->setHandle(chanHandle); + _handleSeed++; + if (handle) + *handle = chanHandle; +} + +void MixerImpl::playStream( + SoundType type, + SoundHandle *handle, + AudioStream *stream, + int id, byte volume, int8 balance, + DisposeAfterUse::Flag autofreeStream, + bool permanent, + bool reverseStereo) { + Common::StackLock lock(_mutex); + + if (stream == 0) { + warning("stream is 0"); + return; + } + + + assert(_mixerReady); + + // Prevent duplicate sounds + if (id != -1) { + for (int i = 0; i != NUM_CHANNELS; i++) + if (_channels[i] != 0 && _channels[i]->getId() == id) { + // Delete the stream if were asked to auto-dispose it. + // Note: This could cause trouble if the client code does not + // yet expect the stream to be gone. The primary example to + // keep in mind here is QueuingAudioStream. + // Thus, as a quick rule of thumb, you should never, ever, + // try to play QueuingAudioStreams with a sound id. + if (autofreeStream == DisposeAfterUse::YES) + delete stream; + return; + } + } + +#ifdef AUDIO_REVERSE_STEREO + reverseStereo = !reverseStereo; +#endif + + // Create the channel + Channel *chan = new Channel(this, type, stream, autofreeStream, reverseStereo, id, permanent); + chan->setVolume(volume); + chan->setBalance(balance); + insertChannel(handle, chan); +} + +void MixerImpl::mixCallback(byte *samples, uint len) { + assert(samples); + + Common::StackLock lock(_mutex); + + int16 *buf = (int16 *)samples; + len >>= 2; + + // Since the mixer callback has been called, the mixer must be ready... + _mixerReady = true; + + // zero the buf + memset(buf, 0, 2 * len * sizeof(int16)); + + // mix all channels + for (int i = 0; i != NUM_CHANNELS; i++) + if (_channels[i]) { + if (_channels[i]->isFinished()) { + delete _channels[i]; + _channels[i] = 0; + } else if (!_channels[i]->isPaused()) + _channels[i]->mix(buf, len); + } +} + +void MixerImpl::stopAll() { + Common::StackLock lock(_mutex); + for (int i = 0; i != NUM_CHANNELS; i++) { + if (_channels[i] != 0 && !_channels[i]->isPermanent()) { + delete _channels[i]; + _channels[i] = 0; + } + } +} + +void MixerImpl::stopID(int id) { + Common::StackLock lock(_mutex); + for (int i = 0; i != NUM_CHANNELS; i++) { + if (_channels[i] != 0 && _channels[i]->getId() == id) { + delete _channels[i]; + _channels[i] = 0; + } + } +} + +void MixerImpl::stopHandle(SoundHandle handle) { + Common::StackLock lock(_mutex); + + // Simply ignore stop requests for handles of sounds that already terminated + const int index = handle._val % NUM_CHANNELS; + if (!_channels[index] || _channels[index]->getHandle()._val != handle._val) + return; + + delete _channels[index]; + _channels[index] = 0; +} + +void MixerImpl::setChannelVolume(SoundHandle handle, byte volume) { + Common::StackLock lock(_mutex); + + const int index = handle._val % NUM_CHANNELS; + if (!_channels[index] || _channels[index]->getHandle()._val != handle._val) + return; + + _channels[index]->setVolume(volume); +} + +void MixerImpl::setChannelBalance(SoundHandle handle, int8 balance) { + Common::StackLock lock(_mutex); + + const int index = handle._val % NUM_CHANNELS; + if (!_channels[index] || _channels[index]->getHandle()._val != handle._val) + return; + + _channels[index]->setBalance(balance); +} + +uint32 MixerImpl::getSoundElapsedTime(SoundHandle handle) { + return getElapsedTime(handle).msecs(); +} + +Timestamp MixerImpl::getElapsedTime(SoundHandle handle) { + Common::StackLock lock(_mutex); + + const int index = handle._val % NUM_CHANNELS; + if (!_channels[index] || _channels[index]->getHandle()._val != handle._val) + return Timestamp(0, _sampleRate); + + return _channels[index]->getElapsedTime(); +} + +void MixerImpl::pauseAll(bool paused) { + Common::StackLock lock(_mutex); + for (int i = 0; i != NUM_CHANNELS; i++) { + if (_channels[i] != 0) { + _channels[i]->pause(paused); + } + } +} + +void MixerImpl::pauseID(int id, bool paused) { + Common::StackLock lock(_mutex); + for (int i = 0; i != NUM_CHANNELS; i++) { + if (_channels[i] != 0 && _channels[i]->getId() == id) { + _channels[i]->pause(paused); + return; + } + } +} + +void MixerImpl::pauseHandle(SoundHandle handle, bool paused) { + Common::StackLock lock(_mutex); + + // Simply ignore (un)pause requests for sounds that already terminated + const int index = handle._val % NUM_CHANNELS; + if (!_channels[index] || _channels[index]->getHandle()._val != handle._val) + return; + + _channels[index]->pause(paused); +} + +bool MixerImpl::isSoundIDActive(int id) { + Common::StackLock lock(_mutex); + for (int i = 0; i != NUM_CHANNELS; i++) + if (_channels[i] && _channels[i]->getId() == id) + return true; + return false; +} + +int MixerImpl::getSoundID(SoundHandle handle) { + Common::StackLock lock(_mutex); + const int index = handle._val % NUM_CHANNELS; + if (_channels[index] && _channels[index]->getHandle()._val == handle._val) + return _channels[index]->getId(); + return 0; +} + +bool MixerImpl::isSoundHandleActive(SoundHandle handle) { + Common::StackLock lock(_mutex); + const int index = handle._val % NUM_CHANNELS; + return _channels[index] && _channels[index]->getHandle()._val == handle._val; +} + +bool MixerImpl::hasActiveChannelOfType(SoundType type) { + Common::StackLock lock(_mutex); + for (int i = 0; i != NUM_CHANNELS; i++) + if (_channels[i] && _channels[i]->getType() == type) + return true; + return false; +} + +void MixerImpl::setVolumeForSoundType(SoundType type, int volume) { + assert(0 <= type && type < ARRAYSIZE(_volumeForSoundType)); + + // Check range + if (volume > kMaxMixerVolume) + volume = kMaxMixerVolume; + else if (volume < 0) + volume = 0; + + // TODO: Maybe we should do logarithmic (not linear) volume + // scaling? See also Player_V2::setMasterVolume + + Common::StackLock lock(_mutex); + _volumeForSoundType[type] = volume; + + for (int i = 0; i != NUM_CHANNELS; ++i) { + if (_channels[i] && _channels[i]->getType() == type) + _channels[i]->notifyGlobalVolChange(); + } +} + +int MixerImpl::getVolumeForSoundType(SoundType type) const { + assert(0 <= type && type < ARRAYSIZE(_volumeForSoundType)); + + return _volumeForSoundType[type]; +} + + +#pragma mark - +#pragma mark --- Channel implementations --- +#pragma mark - + +Channel::Channel(Mixer *mixer, Mixer::SoundType type, AudioStream *stream, + DisposeAfterUse::Flag autofreeStream, bool reverseStereo, int id, bool permanent) + : _type(type), _mixer(mixer), _id(id), _permanent(permanent), _volume(Mixer::kMaxChannelVolume), + _balance(0), _pauseLevel(0), _samplesConsumed(0), _samplesDecoded(0), _mixerTimeStamp(0), + _pauseStartTime(0), _pauseTime(0), _autofreeStream(autofreeStream), _converter(0), + _stream(stream) { + assert(mixer); + assert(stream); + + // Get a rate converter instance + _converter = makeRateConverter(_stream->getRate(), mixer->getOutputRate(), _stream->isStereo(), reverseStereo); +} + +Channel::~Channel() { + delete _converter; + if (_autofreeStream == DisposeAfterUse::YES) + delete _stream; +} + +void Channel::setVolume(const byte volume) { + _volume = volume; + updateChannelVolumes(); +} + +void Channel::setBalance(const int8 balance) { + _balance = balance; + updateChannelVolumes(); +} + +void Channel::updateChannelVolumes() { + // From the channel balance/volume and the global volume, we compute + // the effective volume for the left and right channel. Note the + // slightly odd divisor: the 255 reflects the fact that the maximal + // value for _volume is 255, while the 127 is there because the + // balance value ranges from -127 to 127. The mixer (music/sound) + // volume is in the range 0 - kMaxMixerVolume. + // Hence, the vol_l/vol_r values will be in that range, too + + int vol = _mixer->getVolumeForSoundType(_type) * _volume; + + if (_balance == 0) { + _volL = vol / Mixer::kMaxChannelVolume; + _volR = vol / Mixer::kMaxChannelVolume; + } else if (_balance < 0) { + _volL = vol / Mixer::kMaxChannelVolume; + _volR = ((127 + _balance) * vol) / (Mixer::kMaxChannelVolume * 127); + } else { + _volL = ((127 - _balance) * vol) / (Mixer::kMaxChannelVolume * 127); + _volR = vol / Mixer::kMaxChannelVolume; + } +} + +void Channel::pause(bool paused) { + //assert((paused && _pauseLevel >= 0) || (!paused && _pauseLevel)); + + if (paused) { + _pauseLevel++; + + if (_pauseLevel == 1) + _pauseStartTime = g_system->getMillis(); + } else if (_pauseLevel > 0) { + _pauseLevel--; + + if (!_pauseLevel) { + _pauseTime = (g_system->getMillis() - _pauseStartTime); + _pauseStartTime = 0; + } + } +} + +Timestamp Channel::getElapsedTime() { + const uint32 rate = _mixer->getOutputRate(); + uint32 delta = 0; + + Audio::Timestamp ts(0, rate); + + if (_mixerTimeStamp == 0) + return ts; + + if (isPaused()) + delta = _pauseStartTime - _mixerTimeStamp; + else + delta = g_system->getMillis() - _mixerTimeStamp - _pauseTime; + + // Convert the number of samples into a time duration. + + ts = ts.addFrames(_samplesConsumed); + ts = ts.addMsecs(delta); + + // In theory it would seem like a good idea to limit the approximation + // so that it never exceeds the theoretical upper bound set by + // _samplesDecoded. Meanwhile, back in the real world, doing so makes + // the Broken Sword cutscenes noticeably jerkier. I guess the mixer + // isn't invoked at the regular intervals that I first imagined. + + return ts; +} + +void Channel::mix(int16 *data, uint len) { + assert(_stream); + + if (_stream->endOfData()) { + // TODO: call drain method + } else { + assert(_converter); + + _samplesConsumed = _samplesDecoded; + _mixerTimeStamp = g_system->getMillis(); + _pauseTime = 0; + _samplesDecoded += _converter->flow(*_stream, data, len, _volL, _volR); + } +} + +} // End of namespace Audio diff --git a/audio/mixer.h b/audio/mixer.h new file mode 100644 index 0000000000..a048124ca3 --- /dev/null +++ b/audio/mixer.h @@ -0,0 +1,265 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SOUND_MIXER_H +#define SOUND_MIXER_H + +#include "common/types.h" +#include "common/mutex.h" +#include "common/noncopyable.h" + +#include "audio/timestamp.h" + +class OSystem; + + +namespace Audio { + +class AudioStream; +class Channel; +class Mixer; +class MixerImpl; + +/** + * A SoundHandle instances corresponds to a specific sound + * being played via the mixer. It can be used to control that + * sound (pause it, stop it, etc.). + * @see The Mixer class + */ +class SoundHandle { + friend class Channel; + friend class MixerImpl; + uint32 _val; +public: + inline SoundHandle() : _val(0xFFFFFFFF) {} +}; + +/** + * The main audio mixer handles mixing of an arbitrary number of + * audio streams (in the form of AudioStream instances). + */ +class Mixer : Common::NonCopyable { +public: + enum SoundType { + kPlainSoundType = 0, + + kMusicSoundType = 1, + kSFXSoundType = 2, + kSpeechSoundType = 3 + }; + + enum { + kMaxChannelVolume = 255, + kMaxMixerVolume = 256 + }; + +public: + Mixer() {} + virtual ~Mixer() {} + + + + /** + * Is the mixer ready and setup? This may not be the case on systems which + * don't support digital sound output. In that case, the mixer proc may + * never be called. That in turn can cause breakage in games which try to + * sync with an audio stream. In particular, the AdLib MIDI emulation... + * + * @return whether the mixer is ready and setup + * + * @todo get rid of this? + */ + virtual bool isReady() const = 0; + + + /** + * Start playing the given audio stream. + * + * Note that the sound id assigned below is unique. At most one stream + * with a given id can play at any given time. Trying to play a sound + * with an id that is already in use causes the new sound to be not played. + * + * @param type the type (voice/sfx/music) of the stream + * @param handle a SoundHandle which can be used to reference and control + * the stream via suitable mixer methods + * @param stream the actual AudioStream to be played + * @param id a unique id assigned to this stream + * @param volume the volume with which to play the sound, ranging from 0 to 255 + * @param balance the balance with which to play the sound, ranging from -128 to 127 + * @param autofreeStream a flag indicating whether the stream should be + * freed after playback finished + * @param permanent a flag indicating whether a plain stopAll call should + * not stop this particular stream + * @param reverseStereo a flag indicating whether left and right channels shall be swapped + */ + virtual void playStream( + SoundType type, + SoundHandle *handle, + AudioStream *stream, + int id = -1, + byte volume = kMaxChannelVolume, + int8 balance = 0, + DisposeAfterUse::Flag autofreeStream = DisposeAfterUse::YES, + bool permanent = false, + bool reverseStereo = false) = 0; + + /** + * Stop all currently playing sounds. + */ + virtual void stopAll() = 0; + + /** + * Stop playing the sound with given ID. + * + * @param id the ID of the sound to affect + */ + virtual void stopID(int id) = 0; + + /** + * Stop playing the sound corresponding to the given handle. + * + * @param handle the sound to affect + */ + virtual void stopHandle(SoundHandle handle) = 0; + + + + /** + * Pause/unpause all sounds, including all regular and permanent + * channels + * + * @param paused true to pause everything, false to unpause + */ + virtual void pauseAll(bool paused) = 0; + + /** + * Pause/unpause the sound with the given ID. + * + * @param id the ID of the sound to affect + * @param paused true to pause the sound, false to unpause it + */ + virtual void pauseID(int id, bool paused) = 0; + + /** + * Pause/unpause the sound corresponding to the given handle. + * + * @param handle the sound to affect + * @param paused true to pause the sound, false to unpause it + */ + virtual void pauseHandle(SoundHandle handle, bool paused) = 0; + + + + /** + * Check if a sound with the given ID is active. + * + * @param id the ID of the sound to query + * @return true if the sound is active + */ + virtual bool isSoundIDActive(int id) = 0; + + /** + * Get the sound ID of handle sound + * + * @param handle sound to query + * @return sound ID if active + */ + virtual int getSoundID(SoundHandle handle) = 0; + + /** + * Check if a sound with the given handle is active. + * + * @param handle sound to query + * @return true if the sound is active + */ + virtual bool isSoundHandleActive(SoundHandle handle) = 0; + + + + /** + * Set the channel volume for the given handle. + * + * @param handle the sound to affect + * @param volume the new channel volume (0 - kMaxChannelVolume) + */ + virtual void setChannelVolume(SoundHandle handle, byte volume) = 0; + + /** + * Set the channel balance for the given handle. + * + * @param handle the sound to affect + * @param balance the new channel balance: + * (-127 ... 0 ... 127) corresponds to (left ... center ... right) + */ + virtual void setChannelBalance(SoundHandle handle, int8 balance) = 0; + + /** + * Get approximation of for how long the channel has been playing. + */ + virtual uint32 getSoundElapsedTime(SoundHandle handle) = 0; + + /** + * Get approximation of for how long the channel has been playing. + */ + virtual Timestamp getElapsedTime(SoundHandle handle) = 0; + + /** + * Check whether any channel of the given sound type is active. + * For example, this can be used to check whether any SFX sound + * is currently playing, by checking for type kSFXSoundType. + * + * @param type the sound type to look for + * @return true if any channels of the specified type are active. + */ + virtual bool hasActiveChannelOfType(SoundType type) = 0; + + /** + * Set the volume for the given sound type. + * + * @param type the sound type + * @param volume the new global volume, 0 - kMaxMixerVolume + */ + virtual void setVolumeForSoundType(SoundType type, int volume) = 0; + + /** + * Query the global volume. + * + * @param type the sound type + * @return the global music volume, 0 - kMaxMixerVolume + */ + virtual int getVolumeForSoundType(SoundType type) const = 0; + + /** + * Query the system's audio output sample rate. + * + * @return the output sample rate in Hz + */ + virtual uint getOutputRate() const = 0; +}; + + +} // End of namespace Audio + +#endif diff --git a/audio/mixer_intern.h b/audio/mixer_intern.h new file mode 100644 index 0000000000..c8df9a594d --- /dev/null +++ b/audio/mixer_intern.h @@ -0,0 +1,135 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SOUND_MIXER_INTERN_H +#define SOUND_MIXER_INTERN_H + +#include "common/scummsys.h" +#include "common/mutex.h" +#include "audio/mixer.h" + +namespace Audio { + +/** + * The (default) implementation of the ScummVM audio mixing subsystem. + * + * Backends are responsible for allocating (and later releasing) an instance + * of this class, which engines can access via OSystem::getMixer(). + * + * Initialisation of instances of this class usually happens as follows: + * 1) Creat a new Audio::MixerImpl instance. + * 2) Set the hardware output sample rate via the setSampleRate() method. + * 3) Hook up the mixCallback() in a suitable audio processing thread/callback. + * 4) Change the mixer into ready mode via setReady(true). + * 5) Start audio processing (e.g. by resuming the audio thread, if applicable). + * + * In the future, we might make it possible for backends to provide + * (partial) alternative implementations of the mixer, e.g. to make + * better use of native sound mixing support on low-end devices. + * + * @see OSystem::getMixer() + */ +class MixerImpl : public Mixer { +private: + enum { + NUM_CHANNELS = 16 + }; + + OSystem *_syst; + Common::Mutex _mutex; + + const uint _sampleRate; + bool _mixerReady; + uint32 _handleSeed; + + int _volumeForSoundType[4]; + Channel *_channels[NUM_CHANNELS]; + + +public: + + MixerImpl(OSystem *system, uint sampleRate); + ~MixerImpl(); + + virtual bool isReady() const { return _mixerReady; } + + virtual void playStream( + SoundType type, + SoundHandle *handle, + AudioStream *input, + int id, byte volume, int8 balance, + DisposeAfterUse::Flag autofreeStream, + bool permanent, + bool reverseStereo); + + virtual void stopAll(); + virtual void stopID(int id); + virtual void stopHandle(SoundHandle handle); + + virtual void pauseAll(bool paused); + virtual void pauseID(int id, bool paused); + virtual void pauseHandle(SoundHandle handle, bool paused); + + virtual bool isSoundIDActive(int id); + virtual int getSoundID(SoundHandle handle); + + virtual bool isSoundHandleActive(SoundHandle handle); + + virtual void setChannelVolume(SoundHandle handle, byte volume); + virtual void setChannelBalance(SoundHandle handle, int8 balance); + + virtual uint32 getSoundElapsedTime(SoundHandle handle); + virtual Timestamp getElapsedTime(SoundHandle handle); + + virtual bool hasActiveChannelOfType(SoundType type); + + virtual void setVolumeForSoundType(SoundType type, int volume); + virtual int getVolumeForSoundType(SoundType type) const; + + virtual uint getOutputRate() const; + +protected: + void insertChannel(SoundHandle *handle, Channel *chan); + +public: + /** + * The mixer callback function, to be called at regular intervals by + * the backend (e.g. from an audio mixing thread). All the actual mixing + * work is done from here. + */ + void mixCallback(byte *samples, uint len); + + /** + * Set the internal 'is ready' flag of the mixer. + * Backends should invoke Mixer::setReady(true) once initialisation of + * their audio system has been completed. + */ + void setReady(bool ready); +}; + + +} // End of namespace Audio + +#endif diff --git a/audio/mods/infogrames.cpp b/audio/mods/infogrames.cpp new file mode 100644 index 0000000000..27e42c637b --- /dev/null +++ b/audio/mods/infogrames.cpp @@ -0,0 +1,470 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/mods/infogrames.h" +#include "common/endian.h" +#include "common/file.h" +#include "common/memstream.h" + +namespace Audio { + +Infogrames::Instruments::Instruments() { + init(); +} + +Infogrames::Instruments::~Instruments() { + delete[] _sampleData; +} + +void Infogrames::Instruments::init() { + int i; + + for (i = 0; i < 32; i++) { + _samples[i].data = 0; + _samples[i].dataRepeat = 0; + _samples[i].length = 0; + _samples[i].lengthRepeat = 0; + } + _count = 0; + _sampleData = 0; +} + +bool Infogrames::Instruments::load(const char *ins) { + Common::File f; + + if (f.open(ins)) + return load(f); + return false; +} + +bool Infogrames::Instruments::load(Common::SeekableReadStream &ins) { + int i; + int32 fsize; + int32 offset[32]; + int32 offsetRepeat[32]; + int32 dataOffset; + + unload(); + + fsize = ins.readUint32BE(); + dataOffset = fsize; + for (i = 0; (i < 32) && !ins.eos(); i++) { + offset[i] = ins.readUint32BE(); + offsetRepeat[i] = ins.readUint32BE(); + if ((offset[i] > fsize) || (offsetRepeat[i] > fsize) || + (offset[i] < (ins.pos() + 4)) || + (offsetRepeat[i] < (ins.pos() + 4))) { + // Definitely no real entry anymore + ins.seek(-8, SEEK_CUR); + break; + } + + dataOffset = MIN(dataOffset, MIN(offset[i], offsetRepeat[i])); + ins.skip(4); // Unknown + _samples[i].length = ins.readUint16BE() * 2; + _samples[i].lengthRepeat = ins.readUint16BE() * 2; + } + + if (dataOffset >= fsize) + return false; + + _count = i; + _sampleData = new int8[fsize - dataOffset]; + ins.seek(dataOffset + 4); + ins.read(_sampleData, fsize - dataOffset); + + for (i--; i >= 0; i--) { + _samples[i].data = _sampleData + (offset[i] - dataOffset); + _samples[i].dataRepeat = _sampleData + (offsetRepeat[i] - dataOffset); + } + + return true; +} + +void Infogrames::Instruments::unload() { + delete[] _sampleData; + init(); +} + +const uint8 Infogrames::tickCount[] = + {2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96}; +const uint16 Infogrames::periods[] = + {0x6ACC, 0x64CC, 0x5F25, 0x59CE, 0x54C3, 0x5003, 0x4B86, 0x4747, 0x4346, + 0x3F8B, 0x3BF3, 0x3892, 0x3568, 0x3269, 0x2F93, 0x2CEA, 0x2A66, 0x2801, + 0x2566, 0x23A5, 0x21AF, 0x1FC4, 0x1DFE, 0x1C4E, 0x1ABC, 0x1936, 0x17CC, + 0x1676, 0x1533, 0x1401, 0x12E4, 0x11D5, 0x10D4, 0x0FE3, 0x0EFE, 0x0E26, + 0x0D5B, 0x0C9B, 0x0BE5, 0x0B3B, 0x0A9B, 0x0A02, 0x0972, 0x08E9, 0x0869, + 0x07F1, 0x077F, 0x0713, 0x06AD, 0x064D, 0x05F2, 0x059D, 0x054D, 0x0500, + 0x04B8, 0x0475, 0x0435, 0x03F8, 0x03BF, 0x038A, 0x0356, 0x0326, 0x02F9, + 0x02CF, 0x02A6, 0x0280, 0x025C, 0x023A, 0x021A, 0x01FC, 0x01E0, 0x01C5, + 0x01AB, 0x0193, 0x017D, 0x0167, 0x0153, 0x0140, 0x012E, 0x011D, 0x010D, + 0x00FE, 0x00F0, 0x00E2, 0x00D6, 0x00CA, 0x00BE, 0x00B4, 0x00AA, 0x00A0, + 0x0097, 0x008F, 0x0087, 0x007F, 0x0078, 0x0070, 0x0060, 0x0050, 0x0040, + 0x0030, 0x0020, 0x0010, 0x0000, 0x0000, 0x0020, 0x2020, 0x2020, 0x2020, + 0x2020, 0x3030, 0x3030, 0x3020, 0x2020, 0x2020, 0x2020, 0x2020, 0x2020, + 0x2020, 0x2020, 0x2020, 0x2090, 0x4040, 0x4040, 0x4040, 0x4040, 0x4040, + 0x4040, 0x4040, 0x400C, 0x0C0C, 0x0C0C, 0x0C0C, 0x0C0C, 0x0C40, 0x4040, + 0x4040, 0x4040, 0x0909, 0x0909, 0x0909, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x4040, 0x4040, 0x4040, + 0x0A0A, 0x0A0A, 0x0A0A, 0x0202, 0x0202, 0x0202, 0x0202, 0x0202, 0x0202, + 0x0202, 0x0202, 0x0202, 0x0202, 0x4040, 0x4040, 0x2000}; + +Infogrames::Infogrames(Instruments &ins, bool stereo, int rate, + int interruptFreq) : Paula(stereo, rate, interruptFreq) { + _instruments = &ins; + _data = 0; + _repCount = -1; + + reset(); +} + +Infogrames::~Infogrames() { + delete[] _data; +} + +void Infogrames::init() { + int i; + + _volume = 0; + _period = 0; + _sample = 0; + _speedCounter = _speed; + + for (i = 0; i < 4; i++) { + _chn[i].cmds = 0; + _chn[i].cmdBlocks = 0; + _chn[i].volSlide.finetuneNeg = 0; + _chn[i].volSlide.finetunePos = 0; + _chn[i].volSlide.data = 0; + _chn[i].volSlide.amount = 0; + _chn[i].volSlide.dataOffset = 0; + _chn[i].volSlide.flags = 0; + _chn[i].volSlide.curDelay1 = 0; + _chn[i].volSlide.curDelay2 = 0; + _chn[i].periodSlide.finetuneNeg = 0; + _chn[i].periodSlide.finetunePos = 0; + _chn[i].periodSlide.data = 0; + _chn[i].periodSlide.amount = 0; + _chn[i].periodSlide.dataOffset = 0; + _chn[i].periodSlide.flags = 0; + _chn[i].periodSlide.curDelay1 = 0; + _chn[i].periodSlide.curDelay2 = 0; + _chn[i].period = 0; + _chn[i].flags = 0x81; + _chn[i].ticks = 0; + _chn[i].tickCount = 0; + _chn[i].periodMod = 0; + } + + _end = (_data == 0); +} + +void Infogrames::reset() { + int i; + + stopPlay(); + init(); + + _volSlideBlocks = 0; + _periodSlideBlocks = 0; + _subSong = 0; + _cmdBlocks = 0; + _speedCounter = 0; + _speed = 0; + + for (i = 0; i < 4; i++) + _chn[i].cmdBlockIndices = 0; +} + +bool Infogrames::load(const char *dum) { + Common::File f; + + if (f.open(dum)) + return load(f); + return false; +} + +bool Infogrames::load(Common::SeekableReadStream &dum) { + int subSong = 0; + int i; + uint32 size; + + size = dum.size(); + if (size < 20) + return false; + + _data = new uint8[size]; + dum.seek(0); + dum.read(_data, size); + + Common::MemoryReadStream dataStr(_data, size); + + dataStr.seek(subSong * 2); + dataStr.seek(dataStr.readUint16BE()); + _subSong = _data + dataStr.pos(); + if (_subSong > (_data + size)) + return false; + + _speedCounter = dataStr.readUint16BE(); + _speed = _speedCounter; + _volSlideBlocks = _subSong + dataStr.readUint16BE(); + _periodSlideBlocks = _subSong + dataStr.readUint16BE(); + for (i = 0; i < 4; i++) { + _chn[i].cmdBlockIndices = _subSong + dataStr.readUint16BE(); + _chn[i].flags = 0x81; + } + _cmdBlocks = _data + dataStr.pos() + 2; + + if ((_volSlideBlocks > (_data + size)) || + (_periodSlideBlocks > (_data + size)) || + (_chn[0].cmdBlockIndices > (_data + size)) || + (_chn[1].cmdBlockIndices > (_data + size)) || + (_chn[2].cmdBlockIndices > (_data + size)) || + (_chn[3].cmdBlockIndices > (_data + size)) || + (_cmdBlocks > (_data + size))) + return false; + + startPaula(); + return true; +} + +void Infogrames::unload() { + stopPlay(); + + delete[] _data; + _data = 0; + + clearVoices(); + reset(); +} + +void Infogrames::getNextSample(Channel &chn) { + byte *data; + byte cmdBlock = 0; + uint16 cmd; + bool cont = false; + + if (chn.flags & 64) + return; + + if (chn.flags & 1) { + chn.flags &= ~1; + chn.cmdBlocks = chn.cmdBlockIndices; + } else { + chn.flags &= ~1; + if (_speedCounter == 0) + chn.ticks--; + if (chn.ticks != 0) { + _volume = MAX((int16) 0, tune(chn.volSlide, 0)); + _period = tune(chn.periodSlide, chn.period); + return; + } else { + chn.ticks = chn.tickCount; + cont = true; + } + } + + while (1) { + while (cont || ((cmdBlock = *chn.cmdBlocks) != 0xFF)) { + if (!cont) { + chn.cmdBlocks++; + chn.cmds = _subSong + + READ_BE_UINT16(_cmdBlocks + (cmdBlock * 2)); + } else + cont = false; + while ((cmd = *chn.cmds) != 0xFF) { + chn.cmds++; + if (cmd & 128) + { + switch (cmd & 0xE0) { + case 0x80: // 100xxxxx - Set ticks + chn.ticks = tickCount[cmd & 0xF]; + chn.tickCount = tickCount[cmd & 0xF]; + break; + case 0xA0: // 101xxxxx - Set sample + _sample = cmd & 0x1F; + break; + case 0xC0: // 110xxxxx - Set volume slide/finetune + data = _volSlideBlocks + (cmd & 0x1F) * 13; + chn.volSlide.flags = (*data & 0x80) | 1; + chn.volSlide.amount = *data++ & 0x7F; + chn.volSlide.data = data; + chn.volSlide.dataOffset = 0; + chn.volSlide.finetunePos = 0; + chn.volSlide.finetuneNeg = 0; + chn.volSlide.curDelay1 = 0; + chn.volSlide.curDelay2 = 0; + break; + case 0xE0: // 111xxxxx - Extended + switch (cmd & 0x1F) { + case 0: // Set period modifier + chn.periodMod = (int8) *chn.cmds++; + break; + case 1: // Set continuous period slide + chn.periodSlide.data = + _periodSlideBlocks + *chn.cmds++ * 13 + 1; + chn.periodSlide.amount = 0; + chn.periodSlide.dataOffset = 0; + chn.periodSlide.finetunePos = 0; + chn.periodSlide.finetuneNeg = 0; + chn.periodSlide.curDelay1 = 0; + chn.periodSlide.curDelay2 = 0; + chn.periodSlide.flags = 0x81; + break; + case 2: // Set non-continuous period slide + chn.periodSlide.data = + _periodSlideBlocks + *chn.cmds++ * 13 + 1; + chn.periodSlide.amount = 0; + chn.periodSlide.dataOffset = 0; + chn.periodSlide.finetunePos = 0; + chn.periodSlide.finetuneNeg = 0; + chn.periodSlide.curDelay1 = 0; + chn.periodSlide.curDelay2 = 0; + chn.periodSlide.flags = 1; + break; + case 3: // NOP + break; + default: + warning("Unknown Infogrames command: %X", cmd); + } + break; + } + } else { // 0xxxxxxx - Set period + if (cmd != 0) + cmd += chn.periodMod; + chn.period = periods[cmd]; + chn.volSlide.dataOffset = 0; + chn.volSlide.finetunePos = 0; + chn.volSlide.finetuneNeg = 0; + chn.volSlide.curDelay1 = 0; + chn.volSlide.curDelay2 = 0; + chn.volSlide.flags |= 1; + chn.volSlide.flags &= ~4; + chn.periodSlide.dataOffset = 0; + chn.periodSlide.finetunePos = 0; + chn.periodSlide.finetuneNeg = 0; + chn.periodSlide.curDelay1 = 0; + chn.periodSlide.curDelay2 = 0; + chn.periodSlide.flags |= 1; + chn.periodSlide.flags &= ~4; + _volume = MAX((int16) 0, tune(chn.volSlide, 0)); + _period = tune(chn.periodSlide, chn.period); + return; + } + } + } + if (!(chn.flags & 32)) { + chn.flags |= 0x40; + _volume = 0; + return; + } else + chn.cmdBlocks = chn.cmdBlockIndices; + } +} + +int16 Infogrames::tune(Slide &slide, int16 start) const { + byte *data; + uint8 off; + + data = slide.data + slide.dataOffset; + + if (slide.flags & 1) + slide.finetunePos += (int8) data[1]; + slide.flags &= ~1; + + start += slide.finetunePos - slide.finetuneNeg; + if (start < 0) + start = 0; + + if (slide.flags & 4) + return start; + + slide.curDelay1++; + if (slide.curDelay1 != data[2]) + return start; + slide.curDelay2++; + slide.curDelay1 = 0; + if (slide.curDelay2 == data[0]) { + slide.curDelay2 = 0; + off = slide.dataOffset + 3; + if (off == 12) { + if (slide.flags == 0) { + slide.flags |= 4; + return start; + } else { + slide.curDelay2 = 0; + slide.finetuneNeg += slide.amount; + off = 3; + } + } + slide.dataOffset = off; + } + slide.flags |= 1; + return start; +} + +void Infogrames::interrupt() { + int chn; + + if (!_data) { + clearVoices(); + return; + } + + _speedCounter--; + _sample = 0xFF; + for (chn = 0; chn < 4; chn++) { + _volume = 0; + _period = 0; + getNextSample(_chn[chn]); + setChannelVolume(chn, _volume); + setChannelPeriod(chn, _period); + if ((_sample != 0xFF) && (_sample < _instruments->_count)) { + setChannelData(chn, + _instruments->_samples[_sample].data, + _instruments->_samples[_sample].dataRepeat, + _instruments->_samples[_sample].length, + _instruments->_samples[_sample].lengthRepeat); + _sample = 0xFF; + } + } + if (_speedCounter == 0) + _speedCounter = _speed; + + // End reached? + if ((_chn[0].flags & 64) && (_chn[1].flags & 64) && + (_chn[2].flags & 64) && (_chn[3].flags & 64)) { + if (_repCount > 0) { + _repCount--; + init(); + } else if (_repCount != -1) { + stopPaula(); + } else { + init(); + } + } +} + +} // End of namespace Audio diff --git a/audio/mods/infogrames.h b/audio/mods/infogrames.h new file mode 100644 index 0000000000..c7abebf24e --- /dev/null +++ b/audio/mods/infogrames.h @@ -0,0 +1,148 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - gob + */ + +#ifndef SOUND_MODS_INFOGRAMES_H +#define SOUND_MODS_INFOGRAMES_H + +#include "audio/mods/paula.h" +#include "common/stream.h" + +namespace Audio { + +/** A player for the Infogrames/RobHubbard2 format */ +class Infogrames : public Paula { +public: + class Instruments { + public: + Instruments(); + template Instruments(T ins) { + init(); + bool result = load(ins); + assert(result); + } + ~Instruments(); + + bool load(Common::SeekableReadStream &ins); + bool load(const char *ins); + void unload(); + + uint8 getCount() const { return _count; } + + protected: + struct Sample { + int8 *data; + int8 *dataRepeat; + uint32 length; + uint32 lengthRepeat; + } _samples[32]; + + uint8 _count; + int8 *_sampleData; + + void init(); + + friend class Infogrames; + }; + + Infogrames(Instruments &ins, bool stereo = false, int rate = 44100, + int interruptFreq = 0); + ~Infogrames(); + + Instruments *getInstruments() const { return _instruments; } + bool getRepeating() const { return _repCount != 0; } + void setRepeating (int32 repCount) { _repCount = repCount; } + + bool load(Common::SeekableReadStream &dum); + bool load(const char *dum); + void unload(); + void restart() { + if (_data) { + // Use the mutex here to ensure we do not call init() + // while data is being read by the mixer thread. + _mutex.lock(); + init(); + startPlay(); + _mutex.unlock(); + } + } + +protected: + Instruments *_instruments; + + static const uint8 tickCount[]; + static const uint16 periods[]; + byte *_data; + int32 _repCount; + + byte *_subSong; + byte *_cmdBlocks; + byte *_volSlideBlocks; + byte *_periodSlideBlocks; + uint8 _speedCounter; + uint8 _speed; + + uint16 _volume; + int16 _period; + uint8 _sample; + + struct Slide { + byte *data; + int8 amount; + uint8 dataOffset; + int16 finetuneNeg; + int16 finetunePos; + uint8 curDelay1; + uint8 curDelay2; + uint8 flags; // 0: Apply finetune modifier, 2: Don't slide, 7: Continuous + }; + struct Channel { + byte *cmdBlockIndices; + byte *cmdBlocks; + byte *cmds; + uint8 ticks; + uint8 tickCount; + Slide volSlide; + Slide periodSlide; + int16 period; + int8 periodMod; + uint8 flags; // 0: Need init, 5: Loop cmdBlocks, 6: Ignore channel + } _chn[4]; + + void init(); + void reset(); + void getNextSample(Channel &chn); + int16 tune(Slide &slide, int16 start) const; + virtual void interrupt(); +}; + +} // End of namespace Audio + +#endif diff --git a/audio/mods/maxtrax.cpp b/audio/mods/maxtrax.cpp new file mode 100644 index 0000000000..a577c72eed --- /dev/null +++ b/audio/mods/maxtrax.cpp @@ -0,0 +1,1040 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/scummsys.h" +#include "common/endian.h" +#include "common/stream.h" +#include "common/util.h" +#include "common/debug.h" + +#include "audio/mods/maxtrax.h" + +// test for engines using this class. +#if defined(SOUND_MODS_MAXTRAX_H) + +namespace { + +enum { K_VALUE = 0x9fd77, PREF_PERIOD = 0x8fd77, PERIOD_LIMIT = 0x6f73d }; +enum { NO_BEND = 64 << 7, MAX_BEND_RANGE = 24 }; + +int32 precalcNote(byte baseNote, int16 tune, byte octave) { + return K_VALUE + 0x3C000 - ((baseNote << 14) + (tune << 11) / 3) / 3 - (octave << 16); +} + +int32 calcVolumeDelta(int32 delta, uint16 time, uint16 vBlankFreq) { + const int32 div = time * vBlankFreq; + // div <= 1000 means time to small (or even 0) + return (div <= 1000) ? delta : (1000 * delta) / div; +} + +int32 calcTempo(const uint16 tempo, uint16 vBlankFreq) { + return (int32)(((uint32)(tempo & 0xFFF0) << 8) / (uint16)(5 * vBlankFreq)); +} + +void nullFunc(int) {} + +// Function to calculate 2^x, where x is a fixedpoint number with 16 fraction bits +// using exp would be more accurate and needs less space if mathlibrary is already linked +// but this function should be faster and doesnt use floats +#if 1 +inline uint32 pow2Fixed(int32 val) { + static const uint16 tablePow2[] = { + 0, 178, 356, 535, 714, 893, 1073, 1254, 1435, 1617, 1799, 1981, 2164, 2348, 2532, 2716, + 2902, 3087, 3273, 3460, 3647, 3834, 4022, 4211, 4400, 4590, 4780, 4971, 5162, 5353, 5546, 5738, + 5932, 6125, 6320, 6514, 6710, 6906, 7102, 7299, 7496, 7694, 7893, 8092, 8292, 8492, 8693, 8894, + 9096, 9298, 9501, 9704, 9908, 10113, 10318, 10524, 10730, 10937, 11144, 11352, 11560, 11769, 11979, 12189, + 12400, 12611, 12823, 13036, 13249, 13462, 13676, 13891, 14106, 14322, 14539, 14756, 14974, 15192, 15411, 15630, + 15850, 16071, 16292, 16514, 16737, 16960, 17183, 17408, 17633, 17858, 18084, 18311, 18538, 18766, 18995, 19224, + 19454, 19684, 19915, 20147, 20379, 20612, 20846, 21080, 21315, 21550, 21786, 22023, 22260, 22498, 22737, 22977, + 23216, 23457, 23698, 23940, 24183, 24426, 24670, 24915, 25160, 25406, 25652, 25900, 26148, 26396, 26645, 26895, + 27146, 27397, 27649, 27902, 28155, 28409, 28664, 28919, 29175, 29432, 29690, 29948, 30207, 30466, 30727, 30988, + 31249, 31512, 31775, 32039, 32303, 32568, 32834, 33101, 33369, 33637, 33906, 34175, 34446, 34717, 34988, 35261, + 35534, 35808, 36083, 36359, 36635, 36912, 37190, 37468, 37747, 38028, 38308, 38590, 38872, 39155, 39439, 39724, + 40009, 40295, 40582, 40870, 41158, 41448, 41738, 42029, 42320, 42613, 42906, 43200, 43495, 43790, 44087, 44384, + 44682, 44981, 45280, 45581, 45882, 46184, 46487, 46791, 47095, 47401, 47707, 48014, 48322, 48631, 48940, 49251, + 49562, 49874, 50187, 50500, 50815, 51131, 51447, 51764, 52082, 52401, 52721, 53041, 53363, 53685, 54008, 54333, + 54658, 54983, 55310, 55638, 55966, 56296, 56626, 56957, 57289, 57622, 57956, 58291, 58627, 58964, 59301, 59640, + 59979, 60319, 60661, 61003, 61346, 61690, 62035, 62381, 62727, 63075, 63424, 63774, 64124, 64476, 64828, 65182, + 0 + }; + const uint16 whole = val >> 16; + const uint8 index = (uint8)(val >> 8); + // calculate fractional part. + const uint16 base = tablePow2[index]; + // linear interpolation and add 1.0 + uint32 exponent = ((uint32)(uint16)(tablePow2[index + 1] - base) * (uint8)val) + ((uint32)base << 8) + (1 << 24); + + if (whole < 24) { + // shift away all but the last fractional bit which is used for rounding, + // then round to nearest integer + exponent = ((exponent >> (23 - whole)) + 1) >> 1; + } else if (whole < 32) { + // no need to round here + exponent <<= whole - 24; + } else if (val > 0) { + // overflow + exponent = 0xFFFFFFFF; + } else { + // negative integer, test if >= -0.5 + exponent = (val >= -0x8000) ? 1 : 0; + } + return exponent; +} +#else +inline uint32 pow2Fixed(int32 val) { + return (uint32)(expf((float)val * (float)(0.69314718055994530942 / (1 << 16))) + 0.5f); +} +#endif + +} // End of namespace + +namespace Audio { + +MaxTrax::MaxTrax(int rate, bool stereo, uint16 vBlankFreq, uint16 maxScores) + : Paula(stereo, rate, rate / vBlankFreq), + _patch(), + _scores(), + _numScores() { + _playerCtx.maxScoreNum = maxScores; + _playerCtx.vBlankFreq = vBlankFreq; + _playerCtx.frameUnit = (uint16)((1000 << 8) / vBlankFreq); + _playerCtx.scoreIndex = -1; + _playerCtx.volume = 0x40; + + _playerCtx.tempo = 120; + _playerCtx.tempoTime = 0; + _playerCtx.filterOn = true; + _playerCtx.syncCallBack = &nullFunc; + + resetPlayer(); + for (int i = 0; i < ARRAYSIZE(_channelCtx); ++i) + _channelCtx[i].regParamNumber = 0; +} + +MaxTrax::~MaxTrax() { + stopMusic(); + freePatches(); + freeScores(); +} + +void MaxTrax::interrupt() { + // a5 - maxtraxm a4 . globaldata + + // TODO + // test for changes in shared struct and make changes + // specifically all used channels get marked altered + + _playerCtx.ticks += _playerCtx.tickUnit; + const int32 millis = _playerCtx.ticks >> 8; // d4 + + for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { + VoiceContext &voice = _voiceCtx[i]; + if (voice.stopEventTime >= 0) { + assert(voice.channel); + voice.stopEventTime -= (voice.channel < &_channelCtx[kNumChannels]) ? _playerCtx.tickUnit : _playerCtx.frameUnit; + if (voice.stopEventTime <= 0 && voice.status > VoiceContext::kStatusRelease) { + if ((voice.channel->flags & ChannelContext::kFlagDamper) != 0) + voice.hasDamper = true; + else + voice.status = VoiceContext::kStatusRelease; + } + } + } + + if (_playerCtx.scoreIndex >= 0) { + const Event *curEvent = _playerCtx.nextEvent; + int32 eventDelta = _playerCtx.nextEventTime - millis; + for (; eventDelta <= 0; eventDelta += (++curEvent)->startTime) { + const byte cmd = curEvent->command; + ChannelContext &channel = _channelCtx[curEvent->parameter & 0x0F]; + + // outPutEvent(*curEvent); + // debug("CurTime, EventDelta, NextDelta: %d, %d, %d", millis, eventDelta, eventDelta + curEvent[1].startTime ); + + if (cmd < 0x80) { // Note + const int8 voiceIndex = noteOn(channel, cmd, (curEvent->parameter & 0xF0) >> 1, kPriorityScore); + if (voiceIndex >= 0) + _voiceCtx[voiceIndex].stopEventTime = MAX(0, (eventDelta + curEvent->stopTime) << 8); + + } else { + switch (cmd) { + + case 0x80: // TEMPO + if ((_playerCtx.tickUnit >> 8) > curEvent->stopTime) { + _playerCtx.tickUnit = calcTempo(curEvent->parameter << 4, _playerCtx.vBlankFreq); + _playerCtx.tempoTime = 0; + } else { + _playerCtx.tempoStart = _playerCtx.tempo; + _playerCtx.tempoDelta = (curEvent->parameter << 4) - _playerCtx.tempoStart; + _playerCtx.tempoTime = (curEvent->stopTime << 8); + _playerCtx.tempoTicks = 0; + } + break; + + case 0xC0: // PROGRAM + channel.patch = &_patch[curEvent->stopTime & (kNumPatches - 1)]; + break; + + case 0xE0: // BEND + channel.pitchBend = ((curEvent->stopTime & 0x7F00) >> 1) | (curEvent->stopTime & 0x7f); + channel.pitchReal = (((int32)channel.pitchBendRange * channel.pitchBend) >> 5) - (channel.pitchBendRange << 8); + channel.isAltered = true; + break; + + case 0xFF: // END + if (_playerCtx.musicLoop) { + curEvent = _scores[_playerCtx.scoreIndex].events; + eventDelta = curEvent->startTime - millis; + _playerCtx.ticks = 0; + } else + _playerCtx.scoreIndex = -1; + // stop processing for this tick + goto endOfEventLoop; + + case 0xA0: // SPECIAL + switch (curEvent->stopTime >> 8){ + case 0x01: // SPECIAL_SYNC + _playerCtx.syncCallBack(curEvent->stopTime & 0xFF); + break; + case 0x02: // SPECIAL_BEGINREP + // we allow a depth of 4 loops + for (int i = 0; i < ARRAYSIZE(_playerCtx.repeatPoint); ++i) { + if (!_playerCtx.repeatPoint[i]) { + _playerCtx.repeatPoint[i] = curEvent; + _playerCtx.repeatCount[i] = curEvent->stopTime & 0xFF; + break; + } + } + break; + case 0x03: // SPECIAL_ENDREP + for (int i = ARRAYSIZE(_playerCtx.repeatPoint) - 1; i >= 0; --i) { + if (_playerCtx.repeatPoint[i]) { + if (_playerCtx.repeatCount[i]--) + curEvent = _playerCtx.repeatPoint[i]; // gets incremented by 1 at end of loop + else + _playerCtx.repeatPoint[i] = 0; + break; + } + } + break; + } + break; + + case 0xB0: // CONTROL + controlCh(channel, (byte)(curEvent->stopTime >> 8), (byte)curEvent->stopTime); + break; + + default: + debug("Unhandled Command"); + outPutEvent(*curEvent); + } + } + } +endOfEventLoop: + _playerCtx.nextEvent = curEvent; + _playerCtx.nextEventTime = eventDelta + millis; + + // tempoEffect + if (_playerCtx.tempoTime) { + _playerCtx.tempoTicks += _playerCtx.tickUnit; + uint16 newTempo = _playerCtx.tempoStart; + if (_playerCtx.tempoTicks < _playerCtx.tempoTime) { + newTempo += (uint16)((_playerCtx.tempoTicks * _playerCtx.tempoDelta) / _playerCtx.tempoTime); + } else { + _playerCtx.tempoTime = 0; + newTempo += _playerCtx.tempoDelta; + } + _playerCtx.tickUnit = calcTempo(newTempo, _playerCtx.vBlankFreq); + } + } + + // Handling of Envelopes and Portamento + for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { + VoiceContext &voice = _voiceCtx[i]; + if (!voice.channel) + continue; + const ChannelContext &channel = *voice.channel; + const Patch &patch = *voice.patch; + + switch (voice.status) { + case VoiceContext::kStatusSustain: + // we need to check if some voices have no sustainSample. + // in that case they are finished after the attackSample is done + if (voice.dmaOff && Paula::getChannelDmaCount((byte)i) >= voice.dmaOff ) { + voice.dmaOff = 0; + voice.isBlocked = 0; + voice.priority = 0; + // disable it in next tick + voice.stopEventTime = 0; + } + if (!channel.isAltered && !voice.hasPortamento && !channel.modulation) + continue; + // Update Volume and Period + break; + + case VoiceContext::kStatusHalt: + killVoice((byte)i); + continue; + + case VoiceContext::kStatusStart: + if (patch.attackLen) { + voice.envelope = patch.attackPtr; + const uint16 duration = voice.envelope->duration; + voice.envelopeLeft = patch.attackLen; + voice.ticksLeft = duration << 8; + voice.status = VoiceContext::kStatusAttack; + voice.incrVolume = calcVolumeDelta((int32)voice.envelope->volume, duration, _playerCtx.vBlankFreq); + // Process Envelope + } else { + voice.status = VoiceContext::kStatusSustain; + voice.baseVolume = patch.volume; + // Update Volume and Period + } + break; + + case VoiceContext::kStatusRelease: + if (patch.releaseLen) { + voice.envelope = patch.attackPtr + patch.attackLen; + const uint16 duration = voice.envelope->duration; + voice.envelopeLeft = patch.releaseLen; + voice.ticksLeft = duration << 8; + voice.status = VoiceContext::kStatusDecay; + voice.incrVolume = calcVolumeDelta((int32)voice.envelope->volume - voice.baseVolume, duration, _playerCtx.vBlankFreq); + // Process Envelope + } else { + voice.status = VoiceContext::kStatusHalt; + voice.lastVolume = 0; + // Send Audio Packet + } + voice.stopEventTime = -1; + break; + } + + // Process Envelope + const uint16 envUnit = _playerCtx.frameUnit; + if (voice.envelope) { + if (voice.ticksLeft > envUnit) { // envelope still active + voice.baseVolume = (uint16) MIN(MAX(0, voice.baseVolume + voice.incrVolume), 0x8000); + voice.ticksLeft -= envUnit; + // Update Volume and Period + + } else { // next or last Envelope + voice.baseVolume = voice.envelope->volume; + assert(voice.envelopeLeft > 0); + if (--voice.envelopeLeft) { + ++voice.envelope; + const uint16 duration = voice.envelope->duration; + voice.ticksLeft = duration << 8; + voice.incrVolume = calcVolumeDelta((int32)voice.envelope->volume - voice.baseVolume, duration, _playerCtx.vBlankFreq); + // Update Volume and Period + } else if (voice.status == VoiceContext::kStatusDecay) { + voice.status = VoiceContext::kStatusHalt; + voice.envelope = 0; + voice.lastVolume = 0; + // Send Audio Packet + } else { + assert(voice.status == VoiceContext::kStatusAttack); + voice.status = VoiceContext::kStatusSustain; + voice.envelope = 0; + // Update Volume and Period + } + } + } + + // Update Volume and Period + if (voice.status >= VoiceContext::kStatusDecay) { + // Calc volume + uint16 vol = (voice.noteVolume < (1 << 7)) ? (voice.noteVolume * _playerCtx.volume) >> 7 : _playerCtx.volume; + if (voice.baseVolume < (1 << 15)) + vol = (uint16)(((uint32)vol * voice.baseVolume) >> 15); + if (voice.channel->volume < (1 << 7)) + vol = (vol * voice.channel->volume) >> 7; + voice.lastVolume = (byte)MIN(vol, (uint16)0x64); + + // Calc Period + if (voice.hasPortamento) { + voice.portaTicks += envUnit; + if ((uint16)(voice.portaTicks >> 8) >= channel.portamentoTime) { + voice.hasPortamento = false; + voice.baseNote = voice.endNote; + voice.preCalcNote = precalcNote(voice.baseNote, patch.tune, voice.octave); + } + voice.lastPeriod = calcNote(voice); + } else if (channel.isAltered || channel.modulation) + voice.lastPeriod = calcNote(voice); + } + + // Send Audio Packet + Paula::setChannelPeriod((byte)i, (voice.lastPeriod) ? voice.lastPeriod : 1000); + Paula::setChannelVolume((byte)i, (voice.lastPeriod) ? voice.lastVolume : 0); + } + for (ChannelContext *c = _channelCtx; c != &_channelCtx[ARRAYSIZE(_channelCtx)]; ++c) + c->isAltered = false; + +#ifdef MAXTRAX_HAS_MODULATION + // original player had _playerCtx.sineValue = _playerCtx.frameUnit >> 2 + // this should fit the comments that modtime=1000 is one second ? + _playerCtx.sineValue += _playerCtx.frameUnit; +#endif +} + +void MaxTrax::controlCh(ChannelContext &channel, const byte command, const byte data) { + switch (command) { + case 0x01: // modulation level MSB + channel.modulation = data << 8; + break; + case 0x21: // modulation level LSB + channel.modulation = (channel.modulation & 0xFF00) || ((data * 2) & 0xFF); + break; + case 0x05: // portamento time MSB + channel.portamentoTime = data << 7; + break; + case 0x25: // portamento time LSB + channel.portamentoTime = (channel.portamentoTime & 0x3f80) || data; + break; + case 0x06: // data entry MSB + if (channel.regParamNumber == 0) { + channel.pitchBendRange = (int8)MIN((uint8)MAX_BEND_RANGE, (uint8)data); + channel.pitchReal = (((int32)channel.pitchBendRange * channel.pitchBend) >> 5) - (channel.pitchBendRange << 8); + channel.isAltered = true; + } + break; + case 0x07: // Main Volume MSB + channel.volume = (data == 0) ? 0 : data + 1; + channel.isAltered = true; + break; + case 0x0A: // Pan + if (data > 0x40 || (data == 0x40 && ((&channel - _channelCtx) & 1) != 0)) + channel.flags |= ChannelContext::kFlagRightChannel; + else + channel.flags &= ~ChannelContext::kFlagRightChannel; + break; + case 0x10: // GPC as Modulation Time MSB + channel.modulationTime = data << 7; + break; + case 0x30: // GPC as Modulation Time LSB + channel.modulationTime = (channel.modulationTime & 0x3f80) || data; + break; + case 0x11: // GPC as Microtonal Set MSB + channel.microtonal = data << 8; + break; + case 0x31: // GPC as Microtonal Set LSB + channel.microtonal = (channel.microtonal & 0xFF00) || ((data * 2) & 0xFF); + break; + case 0x40: // Damper Pedal + if ((data & 0x40) != 0) + channel.flags |= ChannelContext::kFlagDamper; + else { + channel.flags &= ~ChannelContext::kFlagDamper; + // release all dampered voices on this channel + for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { + if (_voiceCtx[i].channel == &channel && _voiceCtx[i].hasDamper) { + _voiceCtx[i].hasDamper = false; + _voiceCtx[i].status = VoiceContext::kStatusRelease; + } + } + } + break; + case 0x41: // Portamento off/on + if ((data & 0x40) != 0) + channel.flags |= ChannelContext::kFlagPortamento; + else + channel.flags &= ~ChannelContext::kFlagPortamento; + break; + case 0x50: // Microtonal off/on + if ((data & 0x40) != 0) + channel.flags |= ChannelContext::kFlagMicrotonal; + else + channel.flags &= ~ChannelContext::kFlagMicrotonal; + break; + case 0x51: // Audio Filter off/on + Paula::setAudioFilter(data > 0x40 || (data == 0x40 && _playerCtx.filterOn)); + break; + case 0x65: // RPN MSB + channel.regParamNumber = (data << 8) || (channel.regParamNumber & 0xFF); + break; + case 0x64: // RPN LSB + channel.regParamNumber = (channel.regParamNumber & 0xFF00) || data; + break; + case 0x79: // Reset All Controllers + resetChannel(channel, ((&channel - _channelCtx) & 1) != 0); + break; + case 0x7E: // MONO mode + channel.flags |= ChannelContext::kFlagMono; + goto allNotesOff; + case 0x7F: // POLY mode + channel.flags &= ~ChannelContext::kFlagMono; + // Fallthrough + case 0x7B: // All Notes Off +allNotesOff: + for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { + if (_voiceCtx[i].channel == &channel) { + if ((channel.flags & ChannelContext::kFlagDamper) != 0) + _voiceCtx[i].hasDamper = true; + else + _voiceCtx[i].status = VoiceContext::kStatusRelease; + } + } + break; + case 0x78: // All Sounds Off + for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { + if (_voiceCtx[i].channel == &channel) + killVoice((byte)i); + } + break; + } +} + +void MaxTrax::setTempo(const uint16 tempo) { + Common::StackLock lock(_mutex); + _playerCtx.tickUnit = calcTempo(tempo, _playerCtx.vBlankFreq); +} + +void MaxTrax::resetPlayer() { + for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) + killVoice((byte)i); + + for (int i = 0; i < ARRAYSIZE(_channelCtx); ++i) { + _channelCtx[i].flags = 0; + _channelCtx[i].lastNote = (uint8)-1; + resetChannel(_channelCtx[i], (i & 1) != 0); + _channelCtx[i].patch = (i < kNumChannels) ? &_patch[i] : 0; + } + +#ifdef MAXTRAX_HAS_MICROTONAL + for (int i = 0; i < ARRAYSIZE(_microtonal); ++i) + _microtonal[i] = (int16)(i << 8); +#endif +} + +void MaxTrax::stopMusic() { + Common::StackLock lock(_mutex); + _playerCtx.scoreIndex = -1; + for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { + if (_voiceCtx[i].channel < &_channelCtx[kNumChannels]) + killVoice((byte)i); + } +} + +bool MaxTrax::playSong(int songIndex, bool loop) { + if (songIndex < 0 || songIndex >= _numScores) + return false; + Common::StackLock lock(_mutex); + _playerCtx.scoreIndex = -1; + resetPlayer(); + for (int i = 0; i < ARRAYSIZE(_playerCtx.repeatPoint); ++i) + _playerCtx.repeatPoint[i] = 0; + + setTempo(_playerCtx.tempoInitial << 4); + Paula::setAudioFilter(_playerCtx.filterOn); + _playerCtx.musicLoop = loop; + _playerCtx.tempoTime = 0; + _playerCtx.scoreIndex = songIndex; + _playerCtx.ticks = 0; + + _playerCtx.nextEvent = _scores[songIndex].events; + _playerCtx.nextEventTime = _playerCtx.nextEvent->startTime; + + Paula::startPaula(); + return true; +} + +void MaxTrax::advanceSong(int advance) { + Common::StackLock lock(_mutex); + if (_playerCtx.scoreIndex >= 0) { + const Event *cev = _playerCtx.nextEvent; + if (cev) { + for (; advance > 0; --advance) { + // TODO - check for boundaries + for (; cev->command != 0xFF && (cev->command != 0xA0 || (cev->stopTime >> 8) != 0x00); ++cev) + ; // no end_command or special_command + end + } + _playerCtx.nextEvent = cev; + } + } +} + +void MaxTrax::killVoice(byte num) { + VoiceContext &voice = _voiceCtx[num]; + voice.channel = 0; + voice.envelope = 0; + voice.status = VoiceContext::kStatusFree; + voice.isBlocked = 0; + voice.hasDamper = false; + voice.hasPortamento = false; + voice.priority = 0; + voice.stopEventTime = -1; + voice.dmaOff = 0; + voice.lastVolume = 0; + voice.tieBreak = 0; + //voice.uinqueId = 0; + + // "stop" voice, set period to 1, vol to 0 + Paula::disableChannel(num); + Paula::setChannelPeriod(num, 1); + Paula::setChannelVolume(num, 0); +} + +int8 MaxTrax::pickvoice(uint pick, int16 pri) { + enum { kPrioFlagFixedSide = 1 << 3 }; + pick &= 3; + if ((pri & (kPrioFlagFixedSide)) == 0) { + const bool leftSide = (uint)(pick - 1) > 1; + const int leftBest = MIN(_voiceCtx[0].status, _voiceCtx[3].status); + const int rightBest = MIN(_voiceCtx[1].status, _voiceCtx[2].status); + const int sameSide = (leftSide) ? leftBest : rightBest; + const int otherSide = leftBest + rightBest - sameSide; + + if (sameSide > VoiceContext::kStatusRelease && otherSide <= VoiceContext::kStatusRelease) + pick ^= 1; // switches sides + } + pri &= ~kPrioFlagFixedSide; + + for (int i = 2; i > 0; --i) { + VoiceContext *voice = &_voiceCtx[pick]; + VoiceContext *alternate = &_voiceCtx[pick ^ 3]; + + const uint16 voiceVal = voice->status << 8 | voice->lastVolume; + const uint16 altVal = alternate->status << 8 | alternate->lastVolume; + + if (voiceVal + voice->tieBreak > altVal + || voice->isBlocked > alternate->isBlocked) { + + // this is somewhat different to the original player, + // but has a similar result + voice->tieBreak = 0; + alternate->tieBreak = 1; + + pick ^= 3; // switch channels + VoiceContext *tmp = voice; + voice = alternate; + alternate = tmp; + } + + if (voice->isBlocked || voice->priority > pri) { + // if not already done, switch sides and try again + pick ^= 1; + continue; + } + // succeded + return (int8)pick; + } + // failed + debug(5, "MaxTrax: could not find channel for note"); + return -1; +} + +uint16 MaxTrax::calcNote(const VoiceContext &voice) { + const ChannelContext &channel = *voice.channel; + int16 bend = channel.pitchReal; + +#ifdef MAXTRAX_HAS_MICROTONAL + if (voice.hasPortamento) { + if ((channel.flags & ChannelContext::kFlagMicrotonal) != 0) + bend += (int16)(((_microtonal[voice.endNote] - _microtonal[voice.baseNote]) * voice.portaTicks) >> 8) / channel.portamentoTime; + else + bend += (int16)(((int8)(voice.endNote - voice.baseNote)) * voice.portaTicks) / channel.portamentoTime; + } + + if ((channel.flags & ChannelContext::kFlagMicrotonal) != 0) + bend += _microtonal[voice.baseNote]; +#else + if (voice.hasPortamento) + bend += (int16)(((int8)(voice.endNote - voice.baseNote)) * voice.portaTicks) / channel.portamentoTime; +#endif + +#ifdef MAXTRAX_HAS_MODULATION + static const uint8 tableSine[] = { + 0, 5, 12, 18, 24, 30, 37, 43, 49, 55, 61, 67, 73, 79, 85, 91, + 97, 103, 108, 114, 120, 125, 131, 136, 141, 146, 151, 156, 161, 166, 171, 176, + 180, 184, 189, 193, 197, 201, 205, 208, 212, 215, 219, 222, 225, 228, 230, 233, + 236, 238, 240, 242, 244, 246, 247, 249, 250, 251, 252, 253, 254, 254, 255, 255, + 255, 255, 255, 254, 254, 253, 252, 251, 250, 249, 247, 246, 244, 242, 240, 238, + 236, 233, 230, 228, 225, 222, 219, 215, 212, 208, 205, 201, 197, 193, 189, 184, + 180, 176, 171, 166, 161, 156, 151, 146, 141, 136, 131, 125, 120, 114, 108, 103, + 97, 91, 85, 79, 73, 67, 61, 55, 49, 43, 37, 30, 24, 18, 12, 5 + }; + if (channel.modulation) { + if ((channel.flags & ChannelContext::kFlagModVolume) == 0) { + const uint8 sineByte = _playerCtx.sineValue / channel.modulationTime; + const uint8 sineIndex = sineByte & 0x7F; + const int16 modVal = ((uint32)(uint16)(tableSine[sineIndex] + (sineIndex ? 1 : 0)) * channel.modulation) >> 8; + bend = (sineByte < 0x80) ? bend + modVal : bend - modVal; + } + } +#endif + + // tone = voice.baseNote << 8 + microtonal + // bend = channelPitch + porta + modulation + + const int32 tone = voice.preCalcNote + (bend << 6) / 3; + + return (tone >= PERIOD_LIMIT) ? (uint16)pow2Fixed(tone) : 0; +} + +int8 MaxTrax::noteOn(ChannelContext &channel, const byte note, uint16 volume, uint16 pri) { +#ifdef MAXTRAX_HAS_MICROTONAL + if (channel.microtonal >= 0) + _microtonal[note % 127] = channel.microtonal; +#endif + + if (!volume) + return -1; + + const Patch &patch = *channel.patch; + if (!patch.samplePtr || patch.sampleTotalLen == 0) + return -1; + int8 voiceNum = -1; + if ((channel.flags & ChannelContext::kFlagMono) == 0) { + voiceNum = pickvoice((channel.flags & ChannelContext::kFlagRightChannel) != 0 ? 1 : 0, pri); + } else { + VoiceContext *voice = _voiceCtx + ARRAYSIZE(_voiceCtx) - 1; + for (voiceNum = ARRAYSIZE(_voiceCtx) - 1; voiceNum >= 0 && voice->channel != &channel; --voiceNum, --voice) + ; + if (voiceNum < 0) + voiceNum = pickvoice((channel.flags & ChannelContext::kFlagRightChannel) != 0 ? 1 : 0, pri); + else if (voice->status >= VoiceContext::kStatusSustain && (channel.flags & ChannelContext::kFlagPortamento) != 0) { + // reset previous porta + if (voice->hasPortamento) + voice->baseNote = voice->endNote; + voice->preCalcNote = precalcNote(voice->baseNote, patch.tune, voice->octave); + voice->noteVolume = (_playerCtx.handleVolume) ? volume + 1 : 128; + voice->portaTicks = 0; + voice->hasPortamento = true; + voice->endNote = channel.lastNote = note; + return voiceNum; + } + } + + if (voiceNum >= 0) { + VoiceContext &voice = _voiceCtx[voiceNum]; + voice.hasDamper = false; + voice.isBlocked = 0; + voice.hasPortamento = false; + if (voice.channel) + killVoice(voiceNum); + voice.channel = &channel; + voice.patch = &patch; + voice.baseNote = note; + + // always base octave on the note in the command, regardless of porta + const int32 plainNote = precalcNote(note, patch.tune, 0); + // calculate which sample to use + const int useOctave = (plainNote <= PREF_PERIOD) ? 0 : MIN((plainNote + 0xFFFF - PREF_PERIOD) >> 16, patch.sampleOctaves - 1); + voice.octave = (byte)useOctave; + // adjust precalculated value + voice.preCalcNote = plainNote - (useOctave << 16); + + // next calculate the actual period which depends on wether porta is enabled + if (&channel < &_channelCtx[kNumChannels] && (channel.flags & ChannelContext::kFlagPortamento) != 0) { + if ((channel.flags & ChannelContext::kFlagMono) != 0 && channel.lastNote < 0x80 && channel.lastNote != note) { + voice.portaTicks = 0; + voice.baseNote = channel.lastNote; + voice.endNote = note; + voice.hasPortamento = true; + voice.preCalcNote = precalcNote(voice.baseNote, patch.tune, voice.octave); + } + channel.lastNote = note; + } + + voice.lastPeriod = calcNote(voice); + + voice.priority = (byte)pri; + voice.status = VoiceContext::kStatusStart; + + voice.noteVolume = (_playerCtx.handleVolume) ? volume + 1 : 128; + voice.baseVolume = 0; + + // TODO: since the original player is using the OS-functions, more than 1 sample could be queued up already + // get samplestart for the given octave + const int8 *samplePtr = patch.samplePtr + (patch.sampleTotalLen << useOctave) - patch.sampleTotalLen; + if (patch.sampleAttackLen) { + Paula::setChannelSampleStart(voiceNum, samplePtr); + Paula::setChannelSampleLen(voiceNum, (patch.sampleAttackLen << useOctave) / 2); + + Paula::enableChannel(voiceNum); + // wait for dma-clear + } + + if (patch.sampleTotalLen > patch.sampleAttackLen) { + Paula::setChannelSampleStart(voiceNum, samplePtr + (patch.sampleAttackLen << useOctave)); + Paula::setChannelSampleLen(voiceNum, ((patch.sampleTotalLen - patch.sampleAttackLen) << useOctave) / 2); + if (!patch.sampleAttackLen) + Paula::enableChannel(voiceNum); // need to enable channel + // another pointless wait for DMA-Clear??? + + } else { // no sustain sample + // this means we must stop playback after the attacksample finished + // so we queue up an "empty" sample and note that we need to kill the sample after dma finished + Paula::setChannelSampleStart(voiceNum, 0); + Paula::setChannelSampleLen(voiceNum, 0); + Paula::setChannelDmaCount(voiceNum); + voice.dmaOff = 1; + } + + Paula::setChannelPeriod(voiceNum, (voice.lastPeriod) ? voice.lastPeriod : 1000); + Paula::setChannelVolume(voiceNum, 0); + } + return voiceNum; +} + +void MaxTrax::resetChannel(ChannelContext &chan, bool rightChannel) { + chan.modulation = 0; + chan.modulationTime = 1000; + chan.microtonal = -1; + chan.portamentoTime = 500; + chan.pitchBend = NO_BEND; + chan.pitchReal = 0; + chan.pitchBendRange = MAX_BEND_RANGE; + chan.volume = 128; + chan.flags &= ~(ChannelContext::kFlagPortamento | ChannelContext::kFlagMicrotonal | ChannelContext::kFlagRightChannel); + chan.isAltered = true; + if (rightChannel) + chan.flags |= ChannelContext::kFlagRightChannel; +} + +void MaxTrax::freeScores() { + if (_scores) { + for (int i = 0; i < _numScores; ++i) + delete[] _scores[i].events; + delete[] _scores; + _scores = 0; + } + _numScores = 0; + _playerCtx.tempo = 120; + _playerCtx.filterOn = true; +} + +void MaxTrax::freePatches() { + for (int i = 0; i < ARRAYSIZE(_patch); ++i) { + delete[] _patch[i].samplePtr; + delete[] _patch[i].attackPtr; + } + memset(_patch, 0, sizeof(_patch)); +} + +void MaxTrax::setSignalCallback(void (*callback) (int)) { + Common::StackLock lock(_mutex); + _playerCtx.syncCallBack = (callback == 0) ? nullFunc : callback; +} + +int MaxTrax::playNote(byte note, byte patch, uint16 duration, uint16 volume, bool rightSide) { + Common::StackLock lock(_mutex); + assert(patch < ARRAYSIZE(_patch)); + + ChannelContext &channel = _channelCtx[kNumChannels]; + channel.flags = (rightSide) ? ChannelContext::kFlagRightChannel : 0; + channel.isAltered = false; + channel.patch = &_patch[patch]; + const int8 voiceIndex = noteOn(channel, note, (byte)volume, kPriorityNote); + if (voiceIndex >= 0) { + _voiceCtx[voiceIndex].stopEventTime = duration << 8; + Paula::startPaula(); + } + return voiceIndex; +} + +bool MaxTrax::load(Common::SeekableReadStream &musicData, bool loadScores, bool loadSamples) { + Common::StackLock lock(_mutex); + stopMusic(); + if (loadSamples) + freePatches(); + if (loadScores) + freeScores(); + const char *errorMsg = 0; + // 0x0000: 4 Bytes Header "MXTX" + // 0x0004: uint16 tempo + // 0x0006: uint16 flags. bit0 = lowpassfilter, bit1 = attackvolume, bit15 = microtonal + if (musicData.size() < 10 || musicData.readUint32BE() != 0x4D585458) { + warning("Maxtrax: File is not a Maxtrax Module"); + return false; + } + const uint16 songTempo = musicData.readUint16BE(); + const uint16 flags = musicData.readUint16BE(); + if (loadScores) { + _playerCtx.tempoInitial = songTempo; + _playerCtx.filterOn = (flags & 1) != 0; + _playerCtx.handleVolume = (flags & 2) != 0; + } + + if (flags & (1 << 15)) { + debug(5, "Maxtrax: Song has microtonal"); +#ifdef MAXTRAX_HAS_MICROTONAL + if (loadScores) { + for (int i = 0; i < ARRAYSIZE(_microtonal); ++i) + _microtonal[i] = musicData.readUint16BE(); + } else + musicData.skip(128 * 2); +#else + musicData.skip(128 * 2); +#endif + } + + int scoresLoaded = 0; + // uint16 number of Scores + const uint16 scoresInFile = musicData.readUint16BE(); + + if (musicData.err() || musicData.eos()) + goto ioError; + + if (loadScores) { + const uint16 tempScores = MIN(scoresInFile, _playerCtx.maxScoreNum); + Score *curScore = new Score[tempScores]; + if (!curScore) + goto allocError; + _scores = curScore; + + for (scoresLoaded = 0; scoresLoaded < tempScores; ++scoresLoaded, ++curScore) { + const uint32 numEvents = musicData.readUint32BE(); + Event *curEvent = new Event[numEvents]; + if (!curEvent) + goto allocError; + curScore->events = curEvent; + for (int j = numEvents; j > 0; --j, ++curEvent) { + curEvent->command = musicData.readByte(); + curEvent->parameter = musicData.readByte(); + curEvent->startTime = musicData.readUint16BE(); + curEvent->stopTime = musicData.readUint16BE(); + } + curScore->numEvents = numEvents; + } + _numScores = scoresLoaded; + } + + if (loadSamples) { + // skip over remaining scores in file + for (int i = scoresInFile - scoresLoaded; i > 0; --i) + musicData.skip(musicData.readUint32BE() * 6); + + // uint16 number of Samples + const uint16 wavesInFile = musicData.readUint16BE(); + for (int i = wavesInFile; i > 0; --i) { + // load disksample structure + const uint16 number = musicData.readUint16BE(); + assert(number < ARRAYSIZE(_patch)); + + Patch &curPatch = _patch[number]; + if (curPatch.attackPtr || curPatch.samplePtr) { + delete curPatch.attackPtr; + curPatch.attackPtr = 0; + delete curPatch.samplePtr; + curPatch.samplePtr = 0; + } + curPatch.tune = musicData.readSint16BE(); + curPatch.volume = musicData.readUint16BE(); + curPatch.sampleOctaves = musicData.readUint16BE(); + curPatch.sampleAttackLen = musicData.readUint32BE(); + const uint32 sustainLen = musicData.readUint32BE(); + curPatch.sampleTotalLen = curPatch.sampleAttackLen + sustainLen; + // each octave the number of samples doubles. + const uint32 totalSamples = curPatch.sampleTotalLen * ((1 << curPatch.sampleOctaves) - 1); + curPatch.attackLen = musicData.readUint16BE(); + curPatch.releaseLen = musicData.readUint16BE(); + const uint32 totalEnvs = curPatch.attackLen + curPatch.releaseLen; + + // Allocate space for both attack and release Segment. + Envelope *envPtr = new Envelope[totalEnvs]; + if (!envPtr) + goto allocError; + // Attack Segment + curPatch.attackPtr = envPtr; + // Release Segment + // curPatch.releasePtr = envPtr + curPatch.attackLen; + + // Read Attack and Release Segments + for (int j = totalEnvs; j > 0; --j, ++envPtr) { + envPtr->duration = musicData.readUint16BE(); + envPtr->volume = musicData.readUint16BE(); + } + + // read Samples + int8 *allocSamples = new int8[totalSamples]; + if (!allocSamples) + goto allocError; + curPatch.samplePtr = allocSamples; + musicData.read(allocSamples, totalSamples); + } + } + if (!musicData.err() && !musicData.eos()) + return true; +ioError: + errorMsg = "Maxtrax: Encountered IO-Error"; +allocError: + if (!errorMsg) + errorMsg = "Maxtrax: Could not allocate Memory"; + + warning("%s", errorMsg); + if (loadSamples) + freePatches(); + if (loadScores) + freeScores(); + return false; +} + +#if !defined(NDEBUG) && 0 +void MaxTrax::outPutEvent(const Event &ev, int num) { + struct { + byte cmd; + const char *name; + const char *param; + } COMMANDS[] = { + {0x80, "TEMPO ", "TEMPO, N/A "}, + {0xa0, "SPECIAL ", "CHAN, SPEC # | VAL"}, + {0xb0, "CONTROL ", "CHAN, CTRL # | VAL"}, + {0xc0, "PROGRAM ", "CHANNEL, PROG # "}, + {0xe0, "BEND ", "CHANNEL, BEND VALUE"}, + {0xf0, "SYSEX ", "TYPE, SIZE "}, + {0xf8, "REALTIME", "REALTIME, N/A "}, + {0xff, "END ", "N/A, N/A "}, + {0xff, "NOTE ", "VOL | CHAN, STOP"}, + }; + + int i = 0; + for (; i < ARRAYSIZE(COMMANDS) - 1 && ev.command != COMMANDS[i].cmd; ++i) + ; + + if (num == -1) + debug("Event : %02X %s %s %02X %04X %04X", ev.command, COMMANDS[i].name, COMMANDS[i].param, ev.parameter, ev.startTime, ev.stopTime); + else + debug("Event %3d: %02X %s %s %02X %04X %04X", num, ev.command, COMMANDS[i].name, COMMANDS[i].param, ev.parameter, ev.startTime, ev.stopTime); +} + +void MaxTrax::outPutScore(const Score &sc, int num) { + if (num == -1) + debug("score : %i Events", sc.numEvents); + else + debug("score %2d: %i Events", num, sc.numEvents); + for (uint i = 0; i < sc.numEvents; ++i) + outPutEvent(sc.events[i], i); + debug(""); +} +#else +void MaxTrax::outPutEvent(const Event &ev, int num) {} +void MaxTrax::outPutScore(const Score &sc, int num) {} +#endif // #ifndef NDEBUG + +} // End of namespace Audio + +#endif // #if defined(SOUND_MODS_MAXTRAX_H) diff --git a/audio/mods/maxtrax.h b/audio/mods/maxtrax.h new file mode 100644 index 0000000000..2f890afe2d --- /dev/null +++ b/audio/mods/maxtrax.h @@ -0,0 +1,225 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// see if all engines using this class are DISABLED +#if !defined(ENABLE_KYRA) + +// normal Header Guard +#elif !defined SOUND_MODS_MAXTRAX_H +#define SOUND_MODS_MAXTRAX_H + +// #define MAXTRAX_HAS_MODULATION +// #define MAXTRAX_HAS_MICROTONAL + +#include "audio/mods/paula.h" + +namespace Audio { + +class MaxTrax : public Paula { +public: + MaxTrax(int rate, bool stereo, uint16 vBlankFreq = 50, uint16 maxScores = 128); + virtual ~MaxTrax(); + + bool load(Common::SeekableReadStream &musicData, bool loadScores = true, bool loadSamples = true); + bool playSong(int songIndex, bool loop = false); + void advanceSong(int advance = 1); + int playNote(byte note, byte patch, uint16 duration, uint16 volume, bool rightSide); + void setVolume(const byte volume) { Common::StackLock lock(_mutex); _playerCtx.volume = volume; } + void setTempo(const uint16 tempo); + void stopMusic(); + /** + * Set a callback function for sync-events. + * @param callback Callback function, will be called synchronously, so DONT modify the player + * directly in response + */ + void setSignalCallback(void (*callback) (int)); + +protected: + void interrupt(); + +private: + enum { kNumPatches = 64, kNumVoices = 4, kNumChannels = 16, kNumExtraChannels = 1 }; + enum { kPriorityScore, kPriorityNote, kPrioritySound }; + +#ifdef MAXTRAX_HAS_MICROTONAL + int16 _microtonal[128]; +#endif + + struct Event { + uint16 startTime; + uint16 stopTime; + byte command; + byte parameter; + }; + + const struct Score { + const Event *events; + uint32 numEvents; + } *_scores; + + int _numScores; + + struct { + uint32 sineValue; + uint16 vBlankFreq; + int32 ticks; + int32 tickUnit; + uint16 frameUnit; + + uint16 maxScoreNum; + uint16 tempo; + uint16 tempoInitial; + uint16 tempoStart; + int16 tempoDelta; + int32 tempoTime; + int32 tempoTicks; + + byte volume; + + bool filterOn; + bool handleVolume; + bool musicLoop; + + int scoreIndex; + const Event *nextEvent; + int32 nextEventTime; + + void (*syncCallBack) (int); + const Event *repeatPoint[4]; + byte repeatCount[4]; + } _playerCtx; + + struct Envelope { + uint16 duration; + uint16 volume; + }; + + struct Patch { + const Envelope *attackPtr; + //Envelope *releasePtr; + uint16 attackLen; + uint16 releaseLen; + + int16 tune; + uint16 volume; + + // this was the SampleData struct in the assembler source + const int8 *samplePtr; + uint32 sampleTotalLen; + uint32 sampleAttackLen; + uint16 sampleOctaves; + } _patch[kNumPatches]; + + struct ChannelContext { + const Patch *patch; + uint16 regParamNumber; + + uint16 modulation; + uint16 modulationTime; + + int16 microtonal; + + uint16 portamentoTime; + + int16 pitchBend; + int16 pitchReal; + int8 pitchBendRange; + + uint8 volume; +// uint8 voicesActive; + + enum { + kFlagRightChannel = 1 << 0, + kFlagPortamento = 1 << 1, + kFlagDamper = 1 << 2, + kFlagMono = 1 << 3, + kFlagMicrotonal = 1 << 4, + kFlagModVolume = 1 << 5 + }; + byte flags; + bool isAltered; + + uint8 lastNote; +// uint8 program; + + } _channelCtx[kNumChannels + kNumExtraChannels]; + + struct VoiceContext { + ChannelContext *channel; + const Patch *patch; + const Envelope *envelope; +// uint32 uinqueId; + int32 preCalcNote; + uint32 ticksLeft; + int32 portaTicks; + int32 incrVolume; +// int32 periodOffset; + uint16 envelopeLeft; + uint16 noteVolume; + uint16 baseVolume; + uint16 lastPeriod; + byte baseNote; + byte endNote; + byte octave; +// byte number; +// byte link; + enum { + kStatusFree, + kStatusHalt, + kStatusDecay, + kStatusRelease, + kStatusSustain, + kStatusAttack, + kStatusStart + }; + uint8 isBlocked; + uint8 priority; + byte status; + byte lastVolume; + byte tieBreak; + bool hasDamper; + bool hasPortamento; + byte dmaOff; + + int32 stopEventTime; + } _voiceCtx[kNumVoices]; + + void controlCh(ChannelContext &channel, byte command, byte data); + void freePatches(); + void freeScores(); + void resetChannel(ChannelContext &chan, bool rightChannel); + void resetPlayer(); + + int8 pickvoice(uint pick, int16 pri); + uint16 calcNote(const VoiceContext &voice); + int8 noteOn(ChannelContext &channel, byte note, uint16 volume, uint16 pri); + void killVoice(byte num); + + static void outPutEvent(const Event &ev, int num = -1); + static void outPutScore(const Score &sc, int num = -1); +}; +} // End of namespace Audio + +#endif // !defined SOUND_MODS_MAXTRAX_H diff --git a/audio/mods/module.cpp b/audio/mods/module.cpp new file mode 100644 index 0000000000..0da6923b5d --- /dev/null +++ b/audio/mods/module.cpp @@ -0,0 +1,252 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/mods/module.h" + +#include "common/util.h" +#include "common/endian.h" + +namespace Modules { + +const int16 Module::periods[16][60] = { + {1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960 , 906, + 856 , 808 , 762 , 720 , 678 , 640 , 604 , 570 , 538 , 508 , 480 , 453, + 428 , 404 , 381 , 360 , 339 , 320 , 302 , 285 , 269 , 254 , 240 , 226, + 214 , 202 , 190 , 180 , 170 , 160 , 151 , 143 , 135 , 127 , 120 , 113, + 107 , 101 , 95 , 90 , 85 , 80 , 75 , 71 , 67 , 63 , 60 , 56 }, + {1700, 1604, 1514, 1430, 1348, 1274, 1202, 1134, 1070, 1010, 954 , 900, + 850 , 802 , 757 , 715 , 674 , 637 , 601 , 567 , 535 , 505 , 477 , 450, + 425 , 401 , 379 , 357 , 337 , 318 , 300 , 284 , 268 , 253 , 239 , 225, + 213 , 201 , 189 , 179 , 169 , 159 , 150 , 142 , 134 , 126 , 119 , 113, + 106 , 100 , 94 , 89 , 84 , 79 , 75 , 71 , 67 , 63 , 59 , 56 }, + {1688, 1592, 1504, 1418, 1340, 1264, 1194, 1126, 1064, 1004, 948 , 894, + 844 , 796 , 752 , 709 , 670 , 632 , 597 , 563 , 532 , 502 , 474 , 447, + 422 , 398 , 376 , 355 , 335 , 316 , 298 , 282 , 266 , 251 , 237 , 224, + 211 , 199 , 188 , 177 , 167 , 158 , 149 , 141 , 133 , 125 , 118 , 112, + 105 , 99 , 94 , 88 , 83 , 79 , 74 , 70 , 66 , 62 , 59 , 56 }, + {1676, 1582, 1492, 1408, 1330, 1256, 1184, 1118, 1056, 996 , 940 , 888, + 838 , 791 , 746 , 704 , 665 , 628 , 592 , 559 , 528 , 498 , 470 , 444, + 419 , 395 , 373 , 352 , 332 , 314 , 296 , 280 , 264 , 249 , 235 , 222, + 209 , 198 , 187 , 176 , 166 , 157 , 148 , 140 , 132 , 125 , 118 , 111, + 104 , 99 , 93 , 88 , 83 , 78 , 74 , 70 , 66 , 62 , 59 , 55 }, + {1664, 1570, 1482, 1398, 1320, 1246, 1176, 1110, 1048, 990 , 934 , 882, + 832 , 785 , 741 , 699 , 660 , 623 , 588 , 555 , 524 , 495 , 467 , 441, + 416 , 392 , 370 , 350 , 330 , 312 , 294 , 278 , 262 , 247 , 233 , 220, + 208 , 196 , 185 , 175 , 165 , 156 , 147 , 139 , 131 , 124 , 117 , 110, + 104 , 98 , 92 , 87 , 82 , 78 , 73 , 69 , 65 , 62 , 58 , 55 }, + {1652, 1558, 1472, 1388, 1310, 1238, 1168, 1102, 1040, 982 , 926 , 874, + 826 , 779 , 736 , 694 , 655 , 619 , 584 , 551 , 520 , 491 , 463 , 437, + 413 , 390 , 368 , 347 , 328 , 309 , 292 , 276 , 260 , 245 , 232 , 219, + 206 , 195 , 184 , 174 , 164 , 155 , 146 , 138 , 130 , 123 , 116 , 109, + 103 , 97 , 92 , 87 , 82 , 77 , 73 , 69 , 65 , 61 , 58 , 54 }, + {1640, 1548, 1460, 1378, 1302, 1228, 1160, 1094, 1032, 974 , 920 , 868, + 820 , 774 , 730 , 689 , 651 , 614 , 580 , 547 , 516 , 487 , 460 , 434, + 410 , 387 , 365 , 345 , 325 , 307 , 290 , 274 , 258 , 244 , 230 , 217, + 205 , 193 , 183 , 172 , 163 , 154 , 145 , 137 , 129 , 122 , 115 , 109, + 102 , 96 , 91 , 86 , 81 , 77 , 72 , 68 , 64 , 61 , 57 , 54 }, + {1628, 1536, 1450, 1368, 1292, 1220, 1150, 1086, 1026, 968 , 914 , 862, + 814 , 768 , 725 , 684 , 646 , 610 , 575 , 543 , 513 , 484 , 457 , 431, + 407 , 384 , 363 , 342 , 323 , 305 , 288 , 272 , 256 , 242 , 228 , 216, + 204 , 192 , 181 , 171 , 161 , 152 , 144 , 136 , 128 , 121 , 114 , 108, + 102 , 96 , 90 , 85 , 80 , 76 , 72 , 68 , 64 , 60 , 57 , 54 }, + {1814, 1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960, + 907 , 856 , 808 , 762 , 720 , 678 , 640 , 604 , 570 , 538 , 508 , 480, + 453 , 428 , 404 , 381 , 360 , 339 , 320 , 302 , 285 , 269 , 254 , 240, + 226 , 214 , 202 , 190 , 180 , 170 , 160 , 151 , 143 , 135 , 127 , 120, + 113 , 107 , 101 , 95 , 90 , 85 , 80 , 75 , 71 , 67 , 63 , 60 }, + {1800, 1700, 1604, 1514, 1430, 1350, 1272, 1202, 1134, 1070, 1010, 954, + 900 , 850 , 802 , 757 , 715 , 675 , 636 , 601 , 567 , 535 , 505 , 477, + 450 , 425 , 401 , 379 , 357 , 337 , 318 , 300 , 284 , 268 , 253 , 238, + 225 , 212 , 200 , 189 , 179 , 169 , 159 , 150 , 142 , 134 , 126 , 119, + 112 , 106 , 100 , 94 , 89 , 84 , 79 , 75 , 71 , 67 , 63 , 59 }, + {1788, 1688, 1592, 1504, 1418, 1340, 1264, 1194, 1126, 1064, 1004, 948, + 894 , 844 , 796 , 752 , 709 , 670 , 632 , 597 , 563 , 532 , 502 , 474, + 447 , 422 , 398 , 376 , 355 , 335 , 316 , 298 , 282 , 266 , 251 , 237, + 223 , 211 , 199 , 188 , 177 , 167 , 158 , 149 , 141 , 133 , 125 , 118, + 111 , 105 , 99 , 94 , 88 , 83 , 79 , 74 , 70 , 66 , 62 , 59 }, + {1774, 1676, 1582, 1492, 1408, 1330, 1256, 1184, 1118, 1056, 996 , 940, + 887 , 838 , 791 , 746 , 704 , 665 , 628 , 592 , 559 , 528 , 498 , 470, + 444 , 419 , 395 , 373 , 352 , 332 , 314 , 296 , 280 , 264 , 249 , 235, + 222 , 209 , 198 , 187 , 176 , 166 , 157 , 148 , 140 , 132 , 125 , 118, + 111 , 104 , 99 , 93 , 88 , 83 , 78 , 74 , 70 , 66 , 62 , 59 }, + {1762, 1664, 1570, 1482, 1398, 1320, 1246, 1176, 1110, 1048, 988 , 934, + 881 , 832 , 785 , 741 , 699 , 660 , 623 , 588 , 555 , 524 , 494 , 467, + 441 , 416 , 392 , 370 , 350 , 330 , 312 , 294 , 278 , 262 , 247 , 233, + 220 , 208 , 196 , 185 , 175 , 165 , 156 , 147 , 139 , 131 , 123 , 117, + 110 , 104 , 98 , 92 , 87 , 82 , 78 , 73 , 69 , 65 , 61 , 58 }, + {1750, 1652, 1558, 1472, 1388, 1310, 1238, 1168, 1102, 1040, 982 , 926, + 875 , 826 , 779 , 736 , 694 , 655 , 619 , 584 , 551 , 520 , 491 , 463, + 437 , 413 , 390 , 368 , 347 , 328 , 309 , 292 , 276 , 260 , 245 , 232, + 219 , 206 , 195 , 184 , 174 , 164 , 155 , 146 , 138 , 130 , 123 , 116, + 109 , 103 , 97 , 92 , 87 , 82 , 77 , 73 , 69 , 65 , 61 , 58 }, + {1736, 1640, 1548, 1460, 1378, 1302, 1228, 1160, 1094, 1032, 974 , 920, + 868 , 820 , 774 , 730 , 689 , 651 , 614 , 580 , 547 , 516 , 487 , 460, + 434 , 410 , 387 , 365 , 345 , 325 , 307 , 290 , 274 , 258 , 244 , 230, + 217 , 205 , 193 , 183 , 172 , 163 , 154 , 145 , 137 , 129 , 122 , 115, + 108 , 102 , 96 , 91 , 86 , 81 , 77 , 72 , 68 , 64 , 61 , 57 }, + {1724, 1628, 1536, 1450, 1368, 1292, 1220, 1150, 1086, 1026, 968 , 914, + 862 , 814 , 768 , 725 , 684 , 646 , 610 , 575 , 543 , 513 , 484 , 457, + 431 , 407 , 384 , 363 , 342 , 323 , 305 , 288 , 272 , 256 , 242 , 228, + 216 , 203 , 192 , 181 , 171 , 161 , 152 , 144 , 136 , 128 , 121 , 114, + 108 , 101 , 96 , 90 , 85 , 80 , 76 , 72 , 68 , 64 , 60 , 57 }}; + +const uint32 Module::signatures[] = { + MKID_BE('M.K.'), MKID_BE('M!K!'), MKID_BE('FLT4') +}; + +bool Module::load(Common::SeekableReadStream &st, int offs) { + if (offs) { + // Load the module with the common sample data + load(st, 0); + } + + st.seek(offs); + st.read(songname, 20); + songname[20] = '\0'; + + for (int i = 0; i < NUM_SAMPLES; ++i) { + st.read(sample[i].name, 22); + sample[i].name[22] = '\0'; + sample[i].len = 2 * st.readUint16BE(); + + sample[i].finetune = st.readByte(); + assert(sample[i].finetune < 0x10); + + sample[i].vol = st.readByte(); + sample[i].repeat = 2 * st.readUint16BE(); + sample[i].replen = 2 * st.readUint16BE(); + } + + songlen = st.readByte(); + undef = st.readByte(); + + st.read(songpos, 128); + + sig = st.readUint32BE(); + + bool foundSig = false; + for (int i = 0; i < ARRAYSIZE(signatures); i++) { + if (sig == signatures[i]) { + foundSig = true; + break; + } + } + + if (!foundSig) { + warning("No known signature found in protracker module"); + return false; + } + + int maxpattern = 0; + for (int i = 0; i < 128; ++i) + if (maxpattern < songpos[i]) + maxpattern = songpos[i]; + + pattern = new pattern_t[maxpattern + 1]; + + for (int i = 0; i <= maxpattern; ++i) { + for (int j = 0; j < 64; ++j) { + for (int k = 0; k < 4; ++k) { + uint32 note = st.readUint32BE(); + pattern[i][j][k].sample = (note & 0xf0000000) >> 24 | (note & 0x0000f000) >> 12; + pattern[i][j][k].period = (note >> 16) & 0xfff; + pattern[i][j][k].effect = note & 0xfff; + pattern[i][j][k].note = periodToNote((note >> 16) & 0xfff); + } + } + } + + for (int i = 0; i < NUM_SAMPLES; ++i) { + if (offs) { + // Restore information for modules that use common sample data + for (int j = 0; j < NUM_SAMPLES; ++j) { + if (!scumm_stricmp((const char *)commonSamples[j].name, (const char *)sample[i].name)) { + sample[i].len = commonSamples[j].len; + st.seek(commonSamples[j].offs); + break; + } + } + } else { + // Store information for modules that use common sample data + memcpy(commonSamples[i].name, sample[i].name, 22); + commonSamples[i].len = sample[i].len; + commonSamples[i].offs = st.pos(); + + } + + if (!sample[i].len) { + sample[i].data = 0; + } else { + sample[i].data = new int8[sample[i].len]; + st.read((byte *)sample[i].data, sample[i].len); + } + } + + return true; +} + +Module::Module() { + pattern = 0; + for (int i = 0; i < NUM_SAMPLES; ++i) { + sample[i].data = 0; + } +} + +Module::~Module() { + delete[] pattern; + for (int i = 0; i < NUM_SAMPLES; ++i) { + delete[] sample[i].data; + } +} + +byte Module::periodToNote(int16 period, byte finetune) { + int16 diff1; + int16 diff2; + + diff1 = ABS(periods[finetune][0] - period); + if (diff1 == 0) + return 0; + + for (int i = 1; i < 60; i++) { + diff2 = ABS(periods[finetune][i] - period); + if (diff2 == 0) + return i; + else if (diff2 > diff1) + return i-1; + diff1 = diff2; + } + return 59; +} + +int16 Module::noteToPeriod(byte note, byte finetune) { + if (finetune > 15) + finetune = 15; + if (note > 59) + note = 59; + + return periods[finetune][note]; +} + +} // End of namespace Modules diff --git a/audio/mods/module.h b/audio/mods/module.h new file mode 100644 index 0000000000..550b63617e --- /dev/null +++ b/audio/mods/module.h @@ -0,0 +1,90 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SOUND_MODS_MODULE_H +#define SOUND_MODS_MODULE_H + +#include "common/stream.h" + +namespace Modules { + +#include "common/pack-start.h" // START STRUCT PACKING + +struct note_t { + byte sample; + byte note; + uint16 period; + uint16 effect; +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + +typedef note_t pattern_t[64][4]; + +struct sample_t { + byte name[23]; + uint16 len; + byte finetune; + byte vol; + uint16 repeat; + uint16 replen; + int8 *data; +}; + +struct sample_offs { + byte name[23]; + uint16 len; + uint32 offs; +}; + +class Module { +public: + byte songname[21]; + + static const int NUM_SAMPLES = 31; + sample_t sample[NUM_SAMPLES]; + sample_offs commonSamples[NUM_SAMPLES]; + + byte songlen; + byte undef; + byte songpos[128]; + uint32 sig; + pattern_t *pattern; + + Module(); + ~Module(); + + bool load(Common::SeekableReadStream &stream, int offs); + static byte periodToNote(int16 period, byte finetune = 0); + static int16 noteToPeriod(byte note, byte finetune = 0); + +private: + static const int16 periods[16][60]; + static const uint32 signatures[]; +}; + +} // End of namespace Modules + +#endif diff --git a/audio/mods/paula.cpp b/audio/mods/paula.cpp new file mode 100644 index 0000000000..ef841ac9bf --- /dev/null +++ b/audio/mods/paula.cpp @@ -0,0 +1,212 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/mods/paula.h" +#include "audio/null.h" + +namespace Audio { + +Paula::Paula(bool stereo, int rate, uint interruptFreq) : + _stereo(stereo), _rate(rate), _periodScale((double)kPalPaulaClock / rate), _intFreq(interruptFreq) { + + clearVoices(); + _voice[0].panning = 191; + _voice[1].panning = 63; + _voice[2].panning = 63; + _voice[3].panning = 191; + + if (_intFreq == 0) + _intFreq = _rate; + + _curInt = 0; + _timerBase = 1; + _playing = false; + _end = true; +} + +Paula::~Paula() { +} + +void Paula::clearVoice(byte voice) { + assert(voice < NUM_VOICES); + + _voice[voice].data = 0; + _voice[voice].dataRepeat = 0; + _voice[voice].length = 0; + _voice[voice].lengthRepeat = 0; + _voice[voice].period = 0; + _voice[voice].volume = 0; + _voice[voice].offset = Offset(0); + _voice[voice].dmaCount = 0; +} + +int Paula::readBuffer(int16 *buffer, const int numSamples) { + Common::StackLock lock(_mutex); + + memset(buffer, 0, numSamples * 2); + if (!_playing) { + return numSamples; + } + + if (_stereo) + return readBufferIntern(buffer, numSamples); + else + return readBufferIntern(buffer, numSamples); +} + + +template +inline int mixBuffer(int16 *&buf, const int8 *data, Paula::Offset &offset, frac_t rate, int neededSamples, uint bufSize, byte volume, byte panning) { + int samples; + for (samples = 0; samples < neededSamples && offset.int_off < bufSize; ++samples) { + const int32 tmp = ((int32) data[offset.int_off]) * volume; + if (stereo) { + *buf++ += (tmp * (255 - panning)) >> 7; + *buf++ += (tmp * (panning)) >> 7; + } else + *buf++ += tmp; + + // Step to next source sample + offset.rem_off += rate; + if (offset.rem_off >= (frac_t)FRAC_ONE) { + offset.int_off += fracToInt(offset.rem_off); + offset.rem_off &= FRAC_LO_MASK; + } + } + + return samples; +} + +template +int Paula::readBufferIntern(int16 *buffer, const int numSamples) { + int samples = _stereo ? numSamples / 2 : numSamples; + while (samples > 0) { + + // Handle 'interrupts'. This gives subclasses the chance to adjust the channel data + // (e.g. insert new samples, do pitch bending, whatever). + if (_curInt == 0) { + _curInt = _intFreq; + interrupt(); + } + + // Compute how many samples to generate: at most the requested number of samples, + // of course, but we may stop earlier when an 'interrupt' is expected. + const uint nSamples = MIN((uint)samples, _curInt); + + // Loop over the four channels of the emulated Paula chip + for (int voice = 0; voice < NUM_VOICES; voice++) { + // No data, or paused -> skip channel + if (!_voice[voice].data || (_voice[voice].period <= 0)) + continue; + + // The Paula chip apparently run at 7.0937892 MHz in the PAL + // version and at 7.1590905 MHz in the NTSC version. We divide this + // by the requested the requested output sampling rate _rate + // (typically 44.1 kHz or 22.05 kHz) obtaining the value _periodScale. + // This is then divided by the "period" of the channel we are + // processing, to obtain the correct output 'rate'. + frac_t rate = doubleToFrac(_periodScale / _voice[voice].period); + // Cap the volume + _voice[voice].volume = MIN((byte) 0x40, _voice[voice].volume); + + + Channel &ch = _voice[voice]; + int16 *p = buffer; + int neededSamples = nSamples; + assert(ch.offset.int_off < ch.length); + + // Mix the generated samples into the output buffer + neededSamples -= mixBuffer(p, ch.data, ch.offset, rate, neededSamples, ch.length, ch.volume, ch.panning); + + // Wrap around if necessary + if (ch.offset.int_off >= ch.length) { + // Important: Wrap around the offset *before* updating the voice length. + // Otherwise, if length != lengthRepeat we would wrap incorrectly. + // Note: If offset >= 2*len ever occurs, the following would be wrong; + // instead of subtracting, we then should compute the modulus using "%=". + // Since that requires a division and is slow, and shouldn't be necessary + // in practice anyway, we only use subtraction. + ch.offset.int_off -= ch.length; + ch.dmaCount++; + + ch.data = ch.dataRepeat; + ch.length = ch.lengthRepeat; + } + + // If we have not yet generated enough samples, and looping is active: loop! + if (neededSamples > 0 && ch.length > 2) { + // Repeat as long as necessary. + while (neededSamples > 0) { + // Mix the generated samples into the output buffer + neededSamples -= mixBuffer(p, ch.data, ch.offset, rate, neededSamples, ch.length, ch.volume, ch.panning); + + if (ch.offset.int_off >= ch.length) { + // Wrap around. See also the note above. + ch.offset.int_off -= ch.length; + ch.dmaCount++; + } + } + } + + } + buffer += _stereo ? nSamples * 2 : nSamples; + _curInt -= nSamples; + samples -= nSamples; + } + return numSamples; +} + +} // End of namespace Audio + + +// Plugin interface +// (This can only create a null driver since apple II gs support seeems not to be implemented +// and also is not part of the midi driver architecture. But we need the plugin for the options +// menu in the launcher and for MidiDriver::detectDevice() which is more or less used by all engines.) + +class AmigaMusicPlugin : public NullMusicPlugin { +public: + const char *getName() const { + return _s("Amiga Audio Emulator"); + } + + const char *getId() const { + return "amiga"; + } + + MusicDevices getDevices() const; +}; + +MusicDevices AmigaMusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_AMIGA)); + return devices; +} + +//#if PLUGIN_ENABLED_DYNAMIC(AMIGA) + //REGISTER_PLUGIN_DYNAMIC(AMIGA, PLUGIN_TYPE_MUSIC, AmigaMusicPlugin); +//#else + REGISTER_PLUGIN_STATIC(AMIGA, PLUGIN_TYPE_MUSIC, AmigaMusicPlugin); +//#endif diff --git a/audio/mods/paula.h b/audio/mods/paula.h new file mode 100644 index 0000000000..f6f159d5a6 --- /dev/null +++ b/audio/mods/paula.h @@ -0,0 +1,210 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SOUND_MODS_PAULA_H +#define SOUND_MODS_PAULA_H + +#include "audio/audiostream.h" +#include "common/frac.h" +#include "common/mutex.h" + +namespace Audio { + +/** + * Emulation of the "Paula" Amiga music chip + * The interrupt frequency specifies the number of mixed wavesamples between + * calls of the interrupt method + */ +class Paula : public AudioStream { +public: + static const int NUM_VOICES = 4; + enum { + kPalSystemClock = 7093790, + kNtscSystemClock = 7159090, + kPalCiaClock = kPalSystemClock / 10, + kNtscCiaClock = kNtscSystemClock / 10, + kPalPaulaClock = kPalSystemClock / 2, + kNtscPauleClock = kNtscSystemClock / 2 + }; + + /* TODO: Document this */ + struct Offset { + uint int_off; // integral part of the offset + frac_t rem_off; // fractional part of the offset, at least 0 and less than 1 + + explicit Offset(int off = 0) : int_off(off), rem_off(0) {} + }; + + Paula(bool stereo = false, int rate = 44100, uint interruptFreq = 0); + ~Paula(); + + bool playing() const { return _playing; } + void setTimerBaseValue( uint32 ticksPerSecond ) { _timerBase = ticksPerSecond; } + uint32 getTimerBaseValue() { return _timerBase; } + void setSingleInterrupt(uint sampleDelay) { assert(sampleDelay < _intFreq); _curInt = sampleDelay; } + void setSingleInterruptUnscaled(uint timerDelay) { + setSingleInterrupt((uint)(((double)timerDelay * getRate()) / _timerBase)); + } + void setInterruptFreq(uint sampleDelay) { _intFreq = sampleDelay; _curInt = 0; } + void setInterruptFreqUnscaled(uint timerDelay) { + setInterruptFreq((uint)(((double)timerDelay * getRate()) / _timerBase)); + } + void clearVoice(byte voice); + void clearVoices() { for (int i = 0; i < NUM_VOICES; ++i) clearVoice(i); } + void startPlay() { _playing = true; } + void stopPlay() { _playing = false; } + void pausePlay(bool pause) { _playing = !pause; } + +// AudioStream API + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return _stereo; } + bool endOfData() const { return _end; } + int getRate() const { return _rate; } + +protected: + struct Channel { + const int8 *data; + const int8 *dataRepeat; + uint32 length; + uint32 lengthRepeat; + int16 period; + byte volume; + Offset offset; + byte panning; // For stereo mixing: 0 = far left, 255 = far right + int dmaCount; + }; + + bool _end; + Common::Mutex _mutex; + + virtual void interrupt() = 0; + + void startPaula() { + _playing = true; + _end = false; + } + + void stopPaula() { + _playing = false; + _end = true; + } + + void setChannelPanning(byte channel, byte panning) { + assert(channel < NUM_VOICES); + _voice[channel].panning = panning; + } + + void disableChannel(byte channel) { + assert(channel < NUM_VOICES); + _voice[channel].data = 0; + } + + void enableChannel(byte channel) { + assert(channel < NUM_VOICES); + Channel &ch = _voice[channel]; + ch.data = ch.dataRepeat; + ch.length = ch.lengthRepeat; + // actually first 2 bytes are dropped? + ch.offset = Offset(0); + // ch.period = ch.periodRepeat; + } + + void setChannelPeriod(byte channel, int16 period) { + assert(channel < NUM_VOICES); + _voice[channel].period = period; + } + + void setChannelVolume(byte channel, byte volume) { + assert(channel < NUM_VOICES); + _voice[channel].volume = volume; + } + + void setChannelSampleStart(byte channel, const int8 *data) { + assert(channel < NUM_VOICES); + _voice[channel].dataRepeat = data; + } + + void setChannelSampleLen(byte channel, uint32 length) { + assert(channel < NUM_VOICES); + assert(length < 32768/2); + _voice[channel].lengthRepeat = 2 * length; + } + + void setChannelData(uint8 channel, const int8 *data, const int8 *dataRepeat, uint32 length, uint32 lengthRepeat, int32 offset = 0) { + assert(channel < NUM_VOICES); + + Channel &ch = _voice[channel]; + + ch.dataRepeat = data; + ch.lengthRepeat = length; + enableChannel(channel); + ch.offset = Offset(offset); + + ch.dataRepeat = dataRepeat; + ch.lengthRepeat = lengthRepeat; + } + + void setChannelOffset(byte channel, Offset offset) { + assert(channel < NUM_VOICES); + _voice[channel].offset = offset; + } + + Offset getChannelOffset(byte channel) { + assert(channel < NUM_VOICES); + return _voice[channel].offset; + } + + int getChannelDmaCount(byte channel) { + assert(channel < NUM_VOICES); + return _voice[channel].dmaCount; + } + + void setChannelDmaCount(byte channel, int dmaVal = 0) { + assert(channel < NUM_VOICES); + _voice[channel].dmaCount = dmaVal; + } + + void setAudioFilter(bool enable) { + // TODO: implement + } + +private: + Channel _voice[NUM_VOICES]; + + const bool _stereo; + const int _rate; + const double _periodScale; + uint _intFreq; + uint _curInt; + uint32 _timerBase; + bool _playing; + + template + int readBufferIntern(int16 *buffer, const int numSamples); +}; + +} // End of namespace Audio + +#endif diff --git a/audio/mods/protracker.cpp b/audio/mods/protracker.cpp new file mode 100644 index 0000000000..6051338900 --- /dev/null +++ b/audio/mods/protracker.cpp @@ -0,0 +1,466 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/mods/protracker.h" +#include "audio/mods/paula.h" +#include "audio/mods/module.h" + +#include "audio/audiostream.h" + +namespace Modules { + +class ProtrackerStream : public ::Audio::Paula { +private: + Module _module; + + int _tick; + int _row; + int _pos; + + int _speed; + int _bpm; + + // For effect 0xB - Jump To Pattern; + bool _hasJumpToPattern; + int _jumpToPattern; + + // For effect 0xD - PatternBreak; + bool _hasPatternBreak; + int _skipRow; + + // For effect 0xE6 - Pattern Loop + bool _hasPatternLoop; + int _patternLoopCount; + int _patternLoopRow; + + // For effect 0xEE - Pattern Delay + byte _patternDelay; + + static const int16 sinetable[]; + + struct { + byte sample; + uint16 period; + Offset offset; + + byte vol; + byte finetune; + + // For effect 0x0 - Arpeggio + bool arpeggio; + byte arpeggioNotes[3]; + + // For effect 0x3 - Porta to note + uint16 portaToNote; + byte portaToNoteSpeed; + + // For effect 0x4 - Vibrato + int vibrato; + byte vibratoPos; + byte vibratoSpeed; + byte vibratoDepth; + + // For effect 0xED - Delay sample + byte delaySample; + byte delaySampleTick; + } _track[4]; + +public: + ProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo); + +private: + void interrupt(); + + void doPorta(int track) { + if (_track[track].portaToNote && _track[track].portaToNoteSpeed) { + if (_track[track].period < _track[track].portaToNote) { + _track[track].period += _track[track].portaToNoteSpeed; + if (_track[track].period > _track[track].portaToNote) + _track[track].period = _track[track].portaToNote; + } else if (_track[track].period > _track[track].portaToNote) { + _track[track].period -= _track[track].portaToNoteSpeed; + if (_track[track].period < _track[track].portaToNote) + _track[track].period = _track[track].portaToNote; + } + } + } + void doVibrato(int track) { + _track[track].vibrato = + (_track[track].vibratoDepth * sinetable[_track[track].vibratoPos]) / 128; + _track[track].vibratoPos += _track[track].vibratoSpeed; + _track[track].vibratoPos %= 64; + } + void doVolSlide(int track, byte ex, byte ey) { + int vol = _track[track].vol; + if (ex == 0) + vol -= ey; + else if (ey == 0) + vol += ex; + + if (vol < 0) + vol = 0; + else if (vol > 64) + vol = 64; + + _track[track].vol = vol; + } + + void updateRow(); + void updateEffects(); + +}; + +const int16 ProtrackerStream::sinetable[64] = { + 0, 24, 49, 74, 97, 120, 141, 161, + 180, 197, 212, 224, 235, 244, 250, 253, + 255, 253, 250, 244, 235, 224, 212, 197, + 180, 161, 141, 120, 97, 74, 49, 24, + 0, -24, -49, -74, -97, -120, -141, -161, + -180, -197, -212, -224, -235, -244, -250, -253, + -255, -253, -250, -244, -235, -224, -212, -197, + -180, -161, -141, -120, -97, -74, -49, -24 +}; + +ProtrackerStream::ProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo) : + Paula(stereo, rate, rate/50) { + bool result = _module.load(*stream, offs); + assert(result); + + _tick = _row = _pos = 0; + + _speed = 6; + _bpm = 125; + + _hasJumpToPattern = false; + _jumpToPattern = 0; + + _hasPatternBreak = false; + _skipRow = 0; + + _hasPatternLoop = false; + _patternLoopCount = 0; + _patternLoopRow = 0; + + _patternDelay = 0; + + memset(_track, 0, sizeof(_track)); + + startPaula(); +} + +void ProtrackerStream::updateRow() { + for (int track = 0; track < 4; track++) { + _track[track].arpeggio = false; + _track[track].vibrato = 0; + _track[track].delaySampleTick = 0; + const note_t note = + _module.pattern[_module.songpos[_pos]][_row][track]; + + const int effect = note.effect >> 8; + + if (note.sample) { + if (_track[track].sample != note.sample) { + _track[track].vibratoPos = 0; + } + _track[track].sample = note.sample; + _track[track].finetune = _module.sample[note.sample - 1].finetune; + _track[track].vol = _module.sample[note.sample - 1].vol; + } + + if (note.period) { + if (effect != 3 && effect != 5) { + if (_track[track].finetune) + _track[track].period = _module.noteToPeriod(note.note, _track[track].finetune); + else + _track[track].period = note.period; + _track[track].offset = Offset(0); + } + } + + const byte exy = note.effect & 0xff; + const byte ex = (note.effect >> 4) & 0xf; + const byte ey = note.effect & 0xf; + + int vol; + switch (effect) { + case 0x0: + if (exy) { + _track[track].arpeggio = true; + if (note.period) { + _track[track].arpeggioNotes[0] = note.note; + _track[track].arpeggioNotes[1] = note.note + ex; + _track[track].arpeggioNotes[2] = note.note + ey; + } + } + break; + case 0x1: + break; + case 0x2: + break; + case 0x3: + if (note.period) + _track[track].portaToNote = note.period; + if (exy) + _track[track].portaToNoteSpeed = exy; + break; + case 0x4: + if (exy) { + _track[track].vibratoSpeed = ex; + _track[track].vibratoDepth = ey; + } + break; + case 0x5: + doPorta(track); + doVolSlide(track, ex, ey); + break; + case 0x6: + doVibrato(track); + doVolSlide(track, ex, ey); + break; + case 0x9: // Set sample offset + if (exy) { + _track[track].offset = Offset(exy * 256); + setChannelOffset(track, _track[track].offset); + } + break; + case 0xA: + break; + case 0xB: + _hasJumpToPattern = true; + _jumpToPattern = exy; + break; + case 0xC: + _track[track].vol = exy; + break; + case 0xD: + _hasPatternBreak = true; + _skipRow = ex * 10 + ey; + break; + case 0xE: + switch (ex) { + case 0x0: // Switch filters off + break; + case 0x1: // Fine slide up + _track[track].period -= exy; + break; + case 0x2: // Fine slide down + _track[track].period += exy; + break; + case 0x5: // Set finetune + _track[track].finetune = ey; + _module.sample[_track[track].sample].finetune = ey; + if (note.period) { + if (ey) + _track[track].period = _module.noteToPeriod(note.note, ey); + else + _track[track].period = note.period; + } + break; + case 0x6: + if (ey == 0) { + _patternLoopRow = _row; + } else { + _patternLoopCount++; + if (_patternLoopCount <= ey) + _hasPatternLoop = true; + else + _patternLoopCount = 0; + } + break; + case 0x9: + break; // Retrigger note + case 0xA: // Fine volume slide up + vol = _track[track].vol + ey; + if (vol > 64) + vol = 64; + _track[track].vol = vol; + break; + case 0xB: // Fine volume slide down + vol = _track[track].vol - ey; + if (vol < 0) + vol = 0; + _track[track].vol = vol; + break; + case 0xD: // Delay sample + _track[track].delaySampleTick = ey; + _track[track].delaySample = _track[track].sample; + _track[track].sample = 0; + _track[track].vol = 0; + break; + case 0xE: // Pattern delay + _patternDelay = ey; + break; + default: + warning("Unimplemented effect %X", note.effect); + } + break; + + case 0xF: + if (exy < 0x20) { + _speed = exy; + } else { + _bpm = exy; + setInterruptFreq((int) (getRate() / (_bpm * 0.4))); + } + break; + default: + warning("Unimplemented effect %X", note.effect); + } + } +} + +void ProtrackerStream::updateEffects() { + for (int track = 0; track < 4; track++) { + _track[track].vibrato = 0; + + const note_t note = + _module.pattern[_module.songpos[_pos]][_row][track]; + + const int effect = note.effect >> 8; + + const int exy = note.effect & 0xff; + const int ex = (note.effect >> 4) & 0xf; + const int ey = (note.effect) & 0xf; + + switch (effect) { + case 0x0: + if (exy) { + const int idx = (_tick == 1) ? 0 : (_tick % 3); + _track[track].period = + _module.noteToPeriod(_track[track].arpeggioNotes[idx], + _track[track].finetune); + } + break; + case 0x1: + _track[track].period -= exy; + break; + case 0x2: + _track[track].period += exy; + break; + case 0x3: + doPorta(track); + break; + case 0x4: + doVibrato(track); + break; + case 0x5: + doPorta(track); + doVolSlide(track, ex, ey); + break; + case 0x6: + doVibrato(track); + doVolSlide(track, ex, ey); + break; + case 0xA: + doVolSlide(track, ex, ey); + break; + case 0xE: + switch (ex) { + case 0x6: + break; // Pattern loop + case 0x9: // Retrigger note + if (ey && (_tick % ey) == 0) + _track[track].offset = Offset(0); + break; + case 0xD: // Delay sample + if (_tick == _track[track].delaySampleTick) { + _track[track].sample = _track[track].delaySample; + _track[track].offset = Offset(0); + if (_track[track].sample) + _track[track].vol = _module.sample[_track[track].sample - 1].vol; + } + break; + } + break; + } + } +} + +void ProtrackerStream::interrupt() { + int track; + + for (track = 0; track < 4; track++) { + _track[track].offset = getChannelOffset(track); + if (_tick == 0 && _track[track].arpeggio) { + _track[track].period = _module.noteToPeriod(_track[track].arpeggioNotes[0], + _track[track].finetune); + } + } + + if (_tick == 0) { + if (_hasJumpToPattern) { + _hasJumpToPattern = false; + _pos = _jumpToPattern; + _row = 0; + } else if (_hasPatternBreak) { + _hasPatternBreak = false; + _row = _skipRow; + _pos = (_pos + 1) % _module.songlen; + _patternLoopRow = 0; + } else if (_hasPatternLoop) { + _hasPatternLoop = false; + _row = _patternLoopRow; + } + if (_row >= 64) { + _row = 0; + _pos = (_pos + 1) % _module.songlen; + _patternLoopRow = 0; + } + + updateRow(); + } else + updateEffects(); + + _tick = (_tick + 1) % (_speed + _patternDelay * _speed); + if (_tick == 0) { + _row++; + _patternDelay = 0; + } + + for (track = 0; track < 4; track++) { + setChannelVolume(track, _track[track].vol); + setChannelPeriod(track, _track[track].period + _track[track].vibrato); + if (_track[track].sample) { + sample_t &sample = _module.sample[_track[track].sample - 1]; + setChannelData(track, + sample.data, + sample.replen > 2 ? sample.data + sample.repeat : 0, + sample.len, + sample.replen); + setChannelOffset(track, _track[track].offset); + _track[track].sample = 0; + } + } +} + +} // End of namespace Modules + +namespace Audio { + +AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo) { + return new Modules::ProtrackerStream(stream, offs, rate, stereo); +} + +} // End of namespace Audio diff --git a/audio/mods/protracker.h b/audio/mods/protracker.h new file mode 100644 index 0000000000..af722637c7 --- /dev/null +++ b/audio/mods/protracker.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - agos + * - parallaction + */ + +#ifndef SOUND_MODS_PROTRACKER_H +#define SOUND_MODS_PROTRACKER_H + +#include "common/stream.h" + +namespace Audio { + +class AudioStream; + +/* + * Factory function for ProTracker streams. Reads all data from the + * given ReadStream and creates an AudioStream from this. No reference + * to the 'stream' object is kept, so you can safely delete it after + * invoking this factory. + * + * @param stream the ReadStream from which to read the ProTracker data + * @param rate TODO + * @param stereo TODO + * @return a new AudioStream, or NULL, if an error occurred + */ +AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs = 0, int rate = 44100, bool stereo = true); + +} // End of namespace Audio + +#endif diff --git a/audio/mods/rjp1.cpp b/audio/mods/rjp1.cpp new file mode 100644 index 0000000000..7423abb668 --- /dev/null +++ b/audio/mods/rjp1.cpp @@ -0,0 +1,582 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/debug.h" +#include "common/endian.h" + +#include "audio/mods/paula.h" +#include "audio/mods/rjp1.h" +#include "audio/audiostream.h" + +namespace Audio { + +struct Rjp1Channel { + const int8 *waveData; + const int8 *modulatePeriodData; + const int8 *modulateVolumeData; + const int8 *envelopeData; + uint16 volumeScale; + int16 volume; + uint16 modulatePeriodBase; + uint32 modulatePeriodLimit; + uint32 modulatePeriodIndex; + uint16 modulateVolumeBase; + uint32 modulateVolumeLimit; + uint32 modulateVolumeIndex; + uint8 freqStep; + uint32 freqInc; + uint32 freqInit; + const uint8 *noteData; + const uint8 *sequenceOffsets; + const uint8 *sequenceData; + uint8 loopSeqCount; + uint8 loopSeqCur; + uint8 loopSeq2Count; + uint8 loopSeq2Cur; + bool active; + int16 modulatePeriodInit; + int16 modulatePeriodNext; + bool setupNewNote; + int8 envelopeMode; + int8 envelopeScale; + int8 envelopeEnd1; + int8 envelopeEnd2; + int8 envelopeStart; + int8 envelopeVolume; + uint8 currentInstrument; + const int8 *data; + uint16 pos; + uint16 len; + uint16 repeatPos; + uint16 repeatLen; + bool isSfx; +}; + +class Rjp1 : public Paula { +public: + + struct Vars { + int8 *instData; + uint8 *songData[7]; + uint8 activeChannelsMask; + uint8 currentChannel; + int subsongsCount; + int instrumentsCount; + }; + + Rjp1(int rate, bool stereo); + virtual ~Rjp1(); + + bool load(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData); + void unload(); + + void startPattern(int ch, int pat); + void startSong(int song); + +protected: + + void startSequence(uint8 channelNum, uint8 seqNum); + void turnOffChannel(Rjp1Channel *channel); + void playChannel(Rjp1Channel *channel); + void turnOnChannel(Rjp1Channel *channel); + bool executeSfxSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p); + bool executeSongSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p); + void playSongSequence(Rjp1Channel *channel); + void modulateVolume(Rjp1Channel *channel); + void modulatePeriod(Rjp1Channel *channel); + void setupNote(Rjp1Channel *channel, int16 freq); + void setupInstrument(Rjp1Channel *channel, uint8 num); + void setRelease(Rjp1Channel *channel); + void modulateVolumeEnvelope(Rjp1Channel *channel); + void setSustain(Rjp1Channel *channel); + void setDecay(Rjp1Channel *channel); + void modulateVolumeWaveform(Rjp1Channel *channel); + void setVolume(Rjp1Channel *channel); + + void stopPaulaChannel(uint8 channel); + void setupPaulaChannel(uint8 channel, const int8 *waveData, uint16 offset, uint16 len, uint16 repeatPos, uint16 repeatLen); + + virtual void interrupt(); + + Vars _vars; + Rjp1Channel _channelsTable[4]; + + static const int16 _periodsTable[]; + static const int _periodsCount; +}; + +Rjp1::Rjp1(int rate, bool stereo) + : Paula(stereo, rate, rate / 50) { + memset(&_vars, 0, sizeof(_vars)); + memset(_channelsTable, 0, sizeof(_channelsTable)); +} + +Rjp1::~Rjp1() { + unload(); +} + +bool Rjp1::load(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData) { + if (songData->readUint32BE() == MKID_BE('RJP1') && songData->readUint32BE() == MKID_BE('SMOD')) { + for (int i = 0; i < 7; ++i) { + uint32 size = songData->readUint32BE(); + _vars.songData[i] = (uint8 *)malloc(size); + if (!_vars.songData[i]) + return false; + + songData->read(_vars.songData[i], size); + switch (i) { + case 0: + _vars.instrumentsCount = size / 32; + break; + case 1: + break; + case 2: + // sequence index to offsets, 1 per channel + _vars.subsongsCount = size / 4; + break; + case 3: + case 4: + // sequence offsets + break; + case 5: + case 6: + // sequence data + break; + } + } + + if (instrumentsData->readUint32BE() == MKID_BE('RJP1')) { + uint32 size = instrumentsData->size() - 4; + _vars.instData = (int8 *)malloc(size); + if (!_vars.instData) + return false; + + instrumentsData->read(_vars.instData, size); + + } + } + + debug(5, "Rjp1::load() _instrumentsCount = %d _subsongsCount = %d", _vars.instrumentsCount, _vars.subsongsCount); + return true; +} + +void Rjp1::unload() { + for (int i = 0; i < 7; ++i) { + free(_vars.songData[i]); + } + free(_vars.instData); + memset(&_vars, 0, sizeof(_vars)); + memset(_channelsTable, 0, sizeof(_channelsTable)); +} + +void Rjp1::startPattern(int ch, int pat) { + Rjp1Channel *channel = &_channelsTable[ch]; + _vars.activeChannelsMask |= 1 << ch; + channel->sequenceData = READ_BE_UINT32(_vars.songData[4] + pat * 4) + _vars.songData[6]; + channel->loopSeqCount = 6; + channel->loopSeqCur = channel->loopSeq2Cur = 1; + channel->active = true; + channel->isSfx = true; + // "start" Paula audiostream + startPaula(); +} + +void Rjp1::startSong(int song) { + if (song == 0 || song >= _vars.subsongsCount) { + warning("Invalid subsong number %d, defaulting to 1", song); + song = 1; + } + const uint8 *p = _vars.songData[2] + (song & 0x3F) * 4; + for (int i = 0; i < 4; ++i) { + uint8 seq = *p++; + if (seq) { + startSequence(i, seq); + } + } + // "start" Paula audiostream + startPaula(); +} + +void Rjp1::startSequence(uint8 channelNum, uint8 seqNum) { + Rjp1Channel *channel = &_channelsTable[channelNum]; + _vars.activeChannelsMask |= 1 << channelNum; + if (seqNum != 0) { + const uint8 *p = READ_BE_UINT32(_vars.songData[3] + seqNum * 4) + _vars.songData[5]; + uint8 seq = *p++; + channel->sequenceOffsets = p; + channel->sequenceData = READ_BE_UINT32(_vars.songData[4] + seq * 4) + _vars.songData[6]; + channel->loopSeqCount = 6; + channel->loopSeqCur = channel->loopSeq2Cur = 1; + channel->active = true; + } else { + channel->active = false; + turnOffChannel(channel); + } +} + +void Rjp1::turnOffChannel(Rjp1Channel *channel) { + stopPaulaChannel(channel - _channelsTable); +} + +void Rjp1::playChannel(Rjp1Channel *channel) { + if (channel->active) { + turnOnChannel(channel); + if (channel->sequenceData) { + playSongSequence(channel); + } + modulateVolume(channel); + modulatePeriod(channel); + } +} + +void Rjp1::turnOnChannel(Rjp1Channel *channel) { + if (channel->setupNewNote) { + channel->setupNewNote = false; + setupPaulaChannel(channel - _channelsTable, channel->data, channel->pos, channel->len, channel->repeatPos, channel->repeatLen); + } +} + +bool Rjp1::executeSfxSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p) { + bool loop = true; + switch (code & 7) { + case 0: + _vars.activeChannelsMask &= ~(1 << _vars.currentChannel); + loop = false; + stopPaula(); + break; + case 1: + setRelease(channel); + loop = false; + break; + case 2: + channel->loopSeqCount = *p++; + break; + case 3: + channel->loopSeq2Count = *p++; + break; + case 4: + code = *p++; + if (code != 0) { + setupInstrument(channel, code); + } + break; + case 7: + loop = false; + break; + } + return loop; +} + +bool Rjp1::executeSongSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p) { + bool loop = true; + const uint8 *offs; + switch (code & 7) { + case 0: + offs = channel->sequenceOffsets; + channel->loopSeq2Count = 1; + while (1) { + code = *offs++; + if (code != 0) { + channel->sequenceOffsets = offs; + p = READ_BE_UINT32(_vars.songData[4] + code * 4) + _vars.songData[6]; + break; + } else { + code = offs[0]; + if (code == 0) { + p = 0; + channel->active = false; + _vars.activeChannelsMask &= ~(1 << _vars.currentChannel); + loop = false; + break; + } else if (code & 0x80) { + code = offs[1]; + offs = READ_BE_UINT32(_vars.songData[3] + code * 4) + _vars.songData[5]; + } else { + offs -= code; + } + } + } + break; + case 1: + setRelease(channel); + loop = false; + break; + case 2: + channel->loopSeqCount = *p++; + break; + case 3: + channel->loopSeq2Count = *p++; + break; + case 4: + code = *p++; + if (code != 0) { + setupInstrument(channel, code); + } + break; + case 5: + channel->volumeScale = *p++; + break; + case 6: + channel->freqStep = *p++; + channel->freqInc = READ_BE_UINT32(p); p += 4; + channel->freqInit = 0; + break; + case 7: + loop = false; + break; + } + return loop; +} + +void Rjp1::playSongSequence(Rjp1Channel *channel) { + const uint8 *p = channel->sequenceData; + --channel->loopSeqCur; + if (channel->loopSeqCur == 0) { + --channel->loopSeq2Cur; + if (channel->loopSeq2Cur == 0) { + bool loop = true; + do { + uint8 code = *p++; + if (code & 0x80) { + if (channel->isSfx) { + loop = executeSfxSequenceOp(channel, code, p); + } else { + loop = executeSongSequenceOp(channel, code, p); + } + } else { + code >>= 1; + if (code < _periodsCount) { + setupNote(channel, _periodsTable[code]); + } + loop = false; + } + } while (loop); + channel->sequenceData = p; + channel->loopSeq2Cur = channel->loopSeq2Count; + } + channel->loopSeqCur = channel->loopSeqCount; + } +} + +void Rjp1::modulateVolume(Rjp1Channel *channel) { + modulateVolumeEnvelope(channel); + modulateVolumeWaveform(channel); + setVolume(channel); +} + +void Rjp1::modulatePeriod(Rjp1Channel *channel) { + if (channel->modulatePeriodData) { + uint32 per = channel->modulatePeriodIndex; + int period = (channel->modulatePeriodData[per] * channel->modulatePeriodInit) / 128; + period = -period; + if (period < 0) { + period /= 2; + } + channel->modulatePeriodNext = period + channel->modulatePeriodInit; + ++per; + if (per == channel->modulatePeriodLimit) { + per = channel->modulatePeriodBase * 2; + } + channel->modulatePeriodIndex = per; + } + if (channel->freqStep != 0) { + channel->freqInit += channel->freqInc; + --channel->freqStep; + } + setChannelPeriod(channel - _channelsTable, channel->freqInit + channel->modulatePeriodNext); +} + +void Rjp1::setupNote(Rjp1Channel *channel, int16 period) { + const uint8 *note = channel->noteData; + if (note) { + channel->modulatePeriodInit = channel->modulatePeriodNext = period; + channel->freqInit = 0; + const int8 *e = (const int8 *)_vars.songData[1] + READ_BE_UINT16(note + 12); + channel->envelopeData = e; + channel->envelopeStart = e[1]; + channel->envelopeScale = e[1] - e[0]; + channel->envelopeEnd2 = e[2]; + channel->envelopeEnd1 = e[2]; + channel->envelopeMode = 4; + channel->data = channel->waveData; + channel->pos = READ_BE_UINT16(note + 16); + channel->len = channel->pos + READ_BE_UINT16(note + 18); + channel->setupNewNote = true; + } +} + +void Rjp1::setupInstrument(Rjp1Channel *channel, uint8 num) { + if (channel->currentInstrument != num) { + channel->currentInstrument = num; + const uint8 *p = _vars.songData[0] + num * 32; + channel->noteData = p; + channel->repeatPos = READ_BE_UINT16(p + 20); + channel->repeatLen = READ_BE_UINT16(p + 22); + channel->volumeScale = READ_BE_UINT16(p + 14); + channel->modulatePeriodBase = READ_BE_UINT16(p + 24); + channel->modulatePeriodIndex = 0; + channel->modulatePeriodLimit = READ_BE_UINT16(p + 26) * 2; + channel->modulateVolumeBase = READ_BE_UINT16(p + 28); + channel->modulateVolumeIndex = 0; + channel->modulateVolumeLimit = READ_BE_UINT16(p + 30) * 2; + channel->waveData = _vars.instData + READ_BE_UINT32(p); + uint32 off = READ_BE_UINT32(p + 4); + if (off) { + channel->modulatePeriodData = _vars.instData + off; + } + off = READ_BE_UINT32(p + 8); + if (off) { + channel->modulateVolumeData = _vars.instData + off; + } + } +} + +void Rjp1::setRelease(Rjp1Channel *channel) { + const int8 *e = channel->envelopeData; + if (e) { + channel->envelopeStart = 0; + channel->envelopeScale = -channel->envelopeVolume; + channel->envelopeEnd2 = e[5]; + channel->envelopeEnd1 = e[5]; + channel->envelopeMode = -1; + } +} + +void Rjp1::modulateVolumeEnvelope(Rjp1Channel *channel) { + if (channel->envelopeMode) { + int16 es = channel->envelopeScale; + if (es) { + int8 m = channel->envelopeEnd1; + if (m == 0) { + es = 0; + } else { + es *= m; + m = channel->envelopeEnd2; + if (m == 0) { + es = 0; + } else { + es /= m; + } + } + } + channel->envelopeVolume = channel->envelopeStart - es; + --channel->envelopeEnd1; + if (channel->envelopeEnd1 == -1) { + switch (channel->envelopeMode) { + case 0: + break; + case 2: + setSustain(channel); + break; + case 4: + setDecay(channel); + break; + case -1: + setSustain(channel); + break; + default: + error("Unhandled envelope mode %d", channel->envelopeMode); + break; + } + return; + } + } + channel->volume = channel->envelopeVolume; +} + +void Rjp1::setSustain(Rjp1Channel *channel) { + channel->envelopeMode = 0; +} + +void Rjp1::setDecay(Rjp1Channel *channel) { + const int8 *e = channel->envelopeData; + if (e) { + channel->envelopeStart = e[3]; + channel->envelopeScale = e[3] - e[1]; + channel->envelopeEnd2 = e[4]; + channel->envelopeEnd1 = e[4]; + channel->envelopeMode = 2; + } +} + +void Rjp1::modulateVolumeWaveform(Rjp1Channel *channel) { + if (channel->modulateVolumeData) { + uint32 i = channel->modulateVolumeIndex; + channel->volume += channel->modulateVolumeData[i] * channel->volume / 128; + ++i; + if (i == channel->modulateVolumeLimit) { + i = channel->modulateVolumeBase * 2; + } + channel->modulateVolumeIndex = i; + } +} + +void Rjp1::setVolume(Rjp1Channel *channel) { + channel->volume = (channel->volume * channel->volumeScale) / 64; + channel->volume = CLIP(channel->volume, 0, 64); + setChannelVolume(channel - _channelsTable, channel->volume); +} + +void Rjp1::stopPaulaChannel(uint8 channel) { + clearVoice(channel); +} + +void Rjp1::setupPaulaChannel(uint8 channel, const int8 *waveData, uint16 offset, uint16 len, uint16 repeatPos, uint16 repeatLen) { + if (waveData) { + setChannelData(channel, waveData, waveData + repeatPos * 2, len * 2, repeatLen * 2, offset * 2); + } +} + +void Rjp1::interrupt() { + for (int i = 0; i < 4; ++i) { + _vars.currentChannel = i; + playChannel(&_channelsTable[i]); + } +} + +const int16 Rjp1::_periodsTable[] = { + 0x01C5, 0x01E0, 0x01FC, 0x021A, 0x023A, 0x025C, 0x0280, 0x02A6, 0x02D0, + 0x02FA, 0x0328, 0x0358, 0x00E2, 0x00F0, 0x00FE, 0x010D, 0x011D, 0x012E, + 0x0140, 0x0153, 0x0168, 0x017D, 0x0194, 0x01AC, 0x0071, 0x0078, 0x007F, + 0x0087, 0x008F, 0x0097, 0x00A0, 0x00AA, 0x00B4, 0x00BE, 0x00CA, 0x00D6 +}; + +const int Rjp1::_periodsCount = ARRAYSIZE(_periodsTable); + +AudioStream *makeRjp1Stream(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData, int num, int rate, bool stereo) { + Rjp1 *stream = new Rjp1(rate, stereo); + if (stream->load(songData, instrumentsData)) { + if (num < 0) { + stream->startPattern(3, -num); + } else { + stream->startSong(num); + } + return stream; + } + delete stream; + return 0; +} + +} // End of namespace Audio diff --git a/audio/mods/rjp1.h b/audio/mods/rjp1.h new file mode 100644 index 0000000000..e1960921b2 --- /dev/null +++ b/audio/mods/rjp1.h @@ -0,0 +1,50 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - queen + */ + +#ifndef SOUND_MODS_RJP1_H +#define SOUND_MODS_RJP1_H + +#include "common/stream.h" + +namespace Audio { + +class AudioStream; + +/* + * Factory function for RichardJoseph1 modules. Reads all data from the + * given songData and instrumentsData streams and creates an AudioStream + * from this. No references to these stream objects are kept. + */ +AudioStream *makeRjp1Stream(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData, int num, int rate = 44100, bool stereo = true); + +} // End of namespace Audio + +#endif diff --git a/audio/mods/soundfx.cpp b/audio/mods/soundfx.cpp new file mode 100644 index 0000000000..06a1e29514 --- /dev/null +++ b/audio/mods/soundfx.cpp @@ -0,0 +1,275 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/endian.h" + +#include "audio/mods/paula.h" +#include "audio/mods/soundfx.h" +#include "audio/audiostream.h" + +namespace Audio { + +struct SoundFxInstrument { + char name[23]; + uint16 len; + uint8 finetune; + uint8 volume; + uint16 repeatPos; + uint16 repeatLen; + int8 *data; +}; + +class SoundFx : public Paula { +public: + + enum { + NUM_CHANNELS = 4, + NUM_INSTRUMENTS = 15 + }; + + SoundFx(int rate, bool stereo); + virtual ~SoundFx(); + + bool load(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb); + void play(); + +protected: + + void handlePattern(int ch, uint32 pat); + void updateEffects(int ch); + void handleTick(); + + void disablePaulaChannel(uint8 channel); + void setupPaulaChannel(uint8 channel, const int8 *data, uint16 len, uint16 repeatPos, uint16 repeatLen); + + virtual void interrupt(); + + uint8 _ticks; + uint16 _delay; + SoundFxInstrument _instruments[NUM_INSTRUMENTS]; + uint8 _numOrders; + uint8 _curOrder; + uint16 _curPos; + uint8 _ordersTable[128]; + uint8 *_patternData; + uint16 _effects[NUM_CHANNELS]; +}; + +SoundFx::SoundFx(int rate, bool stereo) + : Paula(stereo, rate) { + setTimerBaseValue(kPalCiaClock); + _ticks = 0; + _delay = 0; + memset(_instruments, 0, sizeof(_instruments)); + _numOrders = 0; + _curOrder = 0; + _curPos = 0; + memset(_ordersTable, 0, sizeof(_ordersTable)); + _patternData = 0; + memset(_effects, 0, sizeof(_effects)); +} + +SoundFx::~SoundFx() { + free(_patternData); + for (int i = 0; i < NUM_INSTRUMENTS; ++i) { + free(_instruments[i].data); + } +} + +bool SoundFx::load(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb) { + int instrumentsSize[15]; + if (!loadCb) { + for (int i = 0; i < NUM_INSTRUMENTS; ++i) { + instrumentsSize[i] = data->readUint32BE(); + } + } + uint8 tag[4]; + data->read(tag, 4); + if (memcmp(tag, "SONG", 4) != 0) { + return false; + } + _delay = data->readUint16BE(); + data->skip(7 * 2); + for (int i = 0; i < NUM_INSTRUMENTS; ++i) { + SoundFxInstrument *ins = &_instruments[i]; + data->read(ins->name, 22); ins->name[22] = 0; + ins->len = data->readUint16BE(); + ins->finetune = data->readByte(); + ins->volume = data->readByte(); + ins->repeatPos = data->readUint16BE(); + ins->repeatLen = data->readUint16BE(); + } + _numOrders = data->readByte(); + data->skip(1); + data->read(_ordersTable, 128); + int maxOrder = 0; + for (int i = 0; i < _numOrders; ++i) { + if (_ordersTable[i] > maxOrder) { + maxOrder = _ordersTable[i]; + } + } + int patternSize = (maxOrder + 1) * 4 * 4 * 64; + _patternData = (uint8 *)malloc(patternSize); + if (!_patternData) { + return false; + } + data->read(_patternData, patternSize); + for (int i = 0; i < NUM_INSTRUMENTS; ++i) { + SoundFxInstrument *ins = &_instruments[i]; + if (!loadCb) { + if (instrumentsSize[i] != 0) { + assert(ins->len <= 1 || ins->len * 2 <= instrumentsSize[i]); + assert(ins->repeatLen <= 1 || (ins->repeatPos + ins->repeatLen) * 2 <= instrumentsSize[i]); + ins->data = (int8 *)malloc(instrumentsSize[i]); + if (!ins->data) { + return false; + } + data->read(ins->data, instrumentsSize[i]); + } + } else { + if (ins->name[0]) { + ins->name[8] = '\0'; + ins->data = (int8 *)(*loadCb)(ins->name, 0); + if (!ins->data) { + return false; + } + } + } + } + return true; +} + +void SoundFx::play() { + _curPos = 0; + _curOrder = 0; + _ticks = 0; + setInterruptFreqUnscaled(_delay); + startPaula(); +} + +void SoundFx::handlePattern(int ch, uint32 pat) { + uint16 note1 = pat >> 16; + uint16 note2 = pat & 0xFFFF; + if (note1 == 0xFFFD) { // PIC + _effects[ch] = 0; + return; + } + _effects[ch] = note2; + if (note1 == 0xFFFE) { // STP + disablePaulaChannel(ch); + return; + } + int ins = (note2 & 0xF000) >> 12; + if (ins != 0) { + SoundFxInstrument *i = &_instruments[ins - 1]; + setupPaulaChannel(ch, i->data, i->len, i->repeatPos, i->repeatLen); + int effect = (note2 & 0xF00) >> 8; + int volume = i->volume; + switch (effect) { + case 5: // volume up + volume += (note2 & 0xFF); + if (volume > 63) { + volume = 63; + } + break; + case 6: // volume down + volume -= (note2 & 0xFF); + if (volume < 0) { + volume = 0; + } + break; + } + setChannelVolume(ch, volume); + } + if (note1 != 0) { + setChannelPeriod(ch, note1); + } +} + +void SoundFx::updateEffects(int ch) { + // updateEffects() is a no-op in all Delphine Software games using SoundFx : FW,OS,Cruise,AW + if (_effects[ch] != 0) { + switch (_effects[ch]) { + case 1: // appreggiato + case 2: // pitchbend + case 3: // ledon, enable low-pass filter + case 4: // ledoff, disable low-pass filter + case 7: // set step up + case 8: // set step down + warning("Unhandled effect %d", _effects[ch]); + break; + } + } +} + +void SoundFx::handleTick() { + ++_ticks; + if (_ticks != 6) { + for (int ch = 0; ch < 4; ++ch) { + updateEffects(ch); + } + } else { + _ticks = 0; + const uint8 *patternData = _patternData + _ordersTable[_curOrder] * 1024 + _curPos; + for (int ch = 0; ch < 4; ++ch) { + handlePattern(ch, READ_BE_UINT32(patternData)); + patternData += 4; + } + _curPos += 4 * 4; + if (_curPos >= 1024) { + _curPos = 0; + ++_curOrder; + if (_curOrder == _numOrders) { + stopPaula(); + } + } + } +} + +void SoundFx::disablePaulaChannel(uint8 channel) { + disableChannel(channel); +} + +void SoundFx::setupPaulaChannel(uint8 channel, const int8 *data, uint16 len, uint16 repeatPos, uint16 repeatLen) { + if (data && len > 1) { + setChannelData(channel, data, data + repeatPos * 2, len * 2, repeatLen * 2); + } +} + +void SoundFx::interrupt() { + handleTick(); +} + +AudioStream *makeSoundFxStream(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb, int rate, bool stereo) { + SoundFx *stream = new SoundFx(rate, stereo); + if (stream->load(data, loadCb)) { + stream->play(); + return stream; + } + delete stream; + return 0; +} + +} // End of namespace Audio diff --git a/audio/mods/soundfx.h b/audio/mods/soundfx.h new file mode 100644 index 0000000000..089c19d292 --- /dev/null +++ b/audio/mods/soundfx.h @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - cine + */ + +#ifndef SOUND_MODS_SOUNDFX_H +#define SOUND_MODS_SOUNDFX_H + +#include "common/stream.h" + +namespace Audio { + +class AudioStream; + +typedef byte *(*LoadSoundFxInstrumentCallback)(const char *name, uint32 *size); + +/* + * Factory function for SoundFX modules. Reads all data from the + * given data stream and creates an AudioStream from this (no references to the + * stream object is kept). If loadCb is non 0, then instruments are loaded using + * it, buffers returned are free'd at the end of playback. + */ +AudioStream *makeSoundFxStream(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb, int rate = 44100, bool stereo = true); + +} // End of namespace Audio + +#endif diff --git a/audio/mods/tfmx.cpp b/audio/mods/tfmx.cpp new file mode 100644 index 0000000000..8c69a75ebd --- /dev/null +++ b/audio/mods/tfmx.cpp @@ -0,0 +1,1193 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/scummsys.h" +#include "common/endian.h" +#include "common/stream.h" +#include "common/util.h" +#include "common/debug.h" + +#include "audio/mods/tfmx.h" + +// test for engines using this class. +#if defined(SOUND_MODS_TFMX_H) + +// couple debug-functions +namespace { + +#if 0 +void displayPatternstep(const void * const vptr); +void displayMacroStep(const void * const vptr); +#endif + +static const uint16 noteIntervalls[64] = { + 1710, 1614, 1524, 1438, 1357, 1281, 1209, 1141, 1077, 1017, 960, 908, + 856, 810, 764, 720, 680, 642, 606, 571, 539, 509, 480, 454, + 428, 404, 381, 360, 340, 320, 303, 286, 270, 254, 240, 227, + 214, 202, 191, 180, 170, 160, 151, 143, 135, 127, 120, 113, + 214, 202, 191, 180, 170, 160, 151, 143, 135, 127, 120, 113, + 214, 202, 191, 180 +}; + +} // End of anonymous namespace + +namespace Audio { + +Tfmx::Tfmx(int rate, bool stereo) + : Paula(stereo, rate), + _resource(), + _resourceSample(), + _playerCtx(), + _deleteResource(false) { + + _playerCtx.stopWithLastPattern = false; + + for (int i = 0; i < kNumVoices; ++i) + _channelCtx[i].paulaChannel = (byte)i; + + _playerCtx.volume = 0x40; + _playerCtx.patternSkip = 6; + stopSongImpl(); + + setTimerBaseValue(kPalCiaClock); + setInterruptFreqUnscaled(kPalDefaultCiaVal); +} + +Tfmx::~Tfmx() { + freeResourceDataImpl(); +} + +void Tfmx::interrupt() { + assert(!_end); + ++_playerCtx.tickCount; + + for (int i = 0; i < kNumVoices; ++i) { + if (_channelCtx[i].dmaIntCount) { + // wait for DMA Interupts to happen + int doneDma = getChannelDmaCount(i); + if (doneDma >= _channelCtx[i].dmaIntCount) { + _channelCtx[i].dmaIntCount = 0; + _channelCtx[i].macroRun = true; + } + } + } + + for (int i = 0; i < kNumVoices; ++i) { + ChannelContext &channel = _channelCtx[i]; + + if (channel.sfxLockTime >= 0) + --channel.sfxLockTime; + else { + channel.sfxLocked = false; + channel.customMacroPrio = 0; + } + + // externally queued macros + if (channel.customMacro) { + const byte * const noteCmd = (const byte *)&channel.customMacro; + channel.sfxLocked = false; + noteCommand(noteCmd[0], noteCmd[1], (noteCmd[2] & 0xF0) | (uint8)i, noteCmd[3]); + channel.customMacro = 0; + channel.sfxLocked = (channel.customMacroPrio != 0); + } + + // apply timebased effects on Parameters + if (channel.macroSfxRun > 0) + effects(channel); + + // see if we have to run the macro-program + if (channel.macroRun) { + if (!channel.macroWait) + macroRun(channel); + else + --channel.macroWait; + } + + Paula::setChannelPeriod(i, channel.period); + if (channel.macroSfxRun >= 0) + channel.macroSfxRun = 1; + + // TODO: handling pending DMAOff? + } + + // Patterns are only processed each _playerCtx.timerCount + 1 tick + if (_playerCtx.song >= 0 && !_playerCtx.patternCount--) { + _playerCtx.patternCount = _playerCtx.patternSkip; + advancePatterns(); + } +} + +void Tfmx::effects(ChannelContext &channel) { + // addBegin + if (channel.addBeginLength) { + channel.sampleStart += channel.addBeginDelta; + Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart)); + if (!(--channel.addBeginCount)) { + channel.addBeginCount = channel.addBeginLength; + channel.addBeginDelta = -channel.addBeginDelta; + } + } + + // vibrato + if (channel.vibLength) { + channel.vibValue += channel.vibDelta; + if (--channel.vibCount == 0) { + channel.vibCount = channel.vibLength; + channel.vibDelta = -channel.vibDelta; + } + if (!channel.portaDelta) { + // 16x16 bit multiplication, casts needed for the right results + channel.period = (uint16)(((uint32)channel.refPeriod * (uint16)((1 << 11) + channel.vibValue)) >> 11); + } + } + + // portamento + if (channel.portaDelta && !(--channel.portaCount)) { + channel.portaCount = channel.portaSkip; + + bool resetPorta = true; + const uint16 period = channel.refPeriod; + uint16 portaVal = channel.portaValue; + + if (period > portaVal) { + portaVal = ((uint32)portaVal * (uint16)((1 << 8) + channel.portaDelta)) >> 8; + resetPorta = (period <= portaVal); + + } else if (period < portaVal) { + portaVal = ((uint32)portaVal * (uint16)((1 << 8) - channel.portaDelta)) >> 8; + resetPorta = (period >= portaVal); + } + + if (resetPorta) { + channel.portaDelta = 0; + channel.portaValue = period & 0x7FF; + } else + channel.period = channel.portaValue = portaVal & 0x7FF; + } + + // envelope + if (channel.envSkip && !channel.envCount--) { + channel.envCount = channel.envSkip; + + const int8 endVol = channel.envEndVolume; + int8 volume = channel.volume; + bool resetEnv = true; + + if (endVol > volume) { + volume += channel.envDelta; + resetEnv = endVol <= volume; + } else { + volume -= channel.envDelta; + resetEnv = volume <= 0 || endVol >= volume; + } + + if (resetEnv) { + channel.envSkip = 0; + volume = endVol; + } + channel.volume = volume; + } + + // Fade + if (_playerCtx.fadeDelta && !(--_playerCtx.fadeCount)) { + _playerCtx.fadeCount = _playerCtx.fadeSkip; + + _playerCtx.volume += _playerCtx.fadeDelta; + if (_playerCtx.volume == _playerCtx.fadeEndVolume) + _playerCtx.fadeDelta = 0; + } + + // Volume + const uint8 finVol = _playerCtx.volume * channel.volume >> 6; + Paula::setChannelVolume(channel.paulaChannel, finVol); +} + +void Tfmx::macroRun(ChannelContext &channel) { + bool deferWait = channel.deferWait; + for (;;) { + const byte *const macroPtr = (const byte *)(getMacroPtr(channel.macroOffset) + channel.macroStep); + ++channel.macroStep; + + switch (macroPtr[0]) { + case 0x00: // Reset + DMA Off. Parameters: deferWait, addset, vol + clearEffects(channel); + // FT + case 0x13: // DMA Off. Parameters: deferWait, addset, vol + // TODO: implement PArameters + Paula::disableChannel(channel.paulaChannel); + channel.deferWait = deferWait = (macroPtr[1] != 0); + if (deferWait) { + // if set, then we expect a DMA On in the same tick. + channel.period = 4; + //Paula::setChannelPeriod(channel.paulaChannel, channel.period); + Paula::setChannelSampleLen(channel.paulaChannel, 1); + // in this state we then need to allow some commands that normally + // would halt the macroprogamm to continue instead. + // those commands are: Wait, WaitDMA, AddPrevNote, AddNote, SetNote, + // DMA On is affected aswell + // TODO remember time disabled, remember pending dmaoff?. + } + + if (macroPtr[2] || macroPtr[3]) { + channel.volume = (macroPtr[2] ? 0 : channel.relVol * 3) + macroPtr[3]; + Paula::setChannelVolume(channel.paulaChannel, channel.volume); + } + continue; + + case 0x01: // DMA On + // TODO: Parameter macroPtr[1] - en-/disable effects + channel.dmaIntCount = 0; + if (deferWait) { + // TODO + // there is actually a small delay in the player, but I think that + // only allows to clear DMA-State on real Hardware + } + Paula::setChannelPeriod(channel.paulaChannel, channel.period); + Paula::enableChannel(channel.paulaChannel); + channel.deferWait = deferWait = false; + continue; + + case 0x02: // Set Beginn. Parameters: SampleOffset(L) + channel.addBeginLength = 0; + channel.sampleStart = READ_BE_UINT32(macroPtr) & 0xFFFFFF; + Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart)); + continue; + + case 0x03: // SetLength. Parameters: SampleLength(W) + channel.sampleLen = READ_BE_UINT16(¯oPtr[2]); + Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen); + continue; + + case 0x04: // Wait. Parameters: Ticks to wait(W). + // TODO: some unknown Parameter? (macroPtr[1] & 1) + channel.macroWait = READ_BE_UINT16(¯oPtr[2]); + break; + + case 0x10: // Loop Key Up. Parameters: Loopcount, MacroStep(W) + if (channel.keyUp) + continue; + // FT + case 0x05: // Loop. Parameters: Loopcount, MacroStep(W) + if (channel.macroLoopCount != 0) { + if (channel.macroLoopCount == 0xFF) + channel.macroLoopCount = macroPtr[1]; + channel.macroStep = READ_BE_UINT16(¯oPtr[2]); + } + --channel.macroLoopCount; + continue; + + case 0x06: // Jump. Parameters: MacroIndex, MacroStep(W) + // channel.macroIndex = macroPtr[1] & (kMaxMacroOffsets - 1); + channel.macroOffset = _resource->macroOffset[macroPtr[1] & (kMaxMacroOffsets - 1)]; + channel.macroStep = READ_BE_UINT16(¯oPtr[2]); + channel.macroLoopCount = 0xFF; + continue; + + case 0x07: // Stop Macro + channel.macroRun = false; + --channel.macroStep; + return; + + case 0x08: // AddNote. Parameters: Note, Finetune(W) + setNoteMacro(channel, channel.note + macroPtr[1], READ_BE_UINT16(¯oPtr[2])); + break; + + case 0x09: // SetNote. Parameters: Note, Finetune(W) + setNoteMacro(channel, macroPtr[1], READ_BE_UINT16(¯oPtr[2])); + break; + + case 0x0A: // Clear Effects + clearEffects(channel); + continue; + + case 0x0B: // Portamento. Parameters: count, speed + channel.portaSkip = macroPtr[1]; + channel.portaCount = 1; + // if porta is already running, then keep using old value + if (!channel.portaDelta) + channel.portaValue = channel.refPeriod; + channel.portaDelta = READ_BE_UINT16(¯oPtr[2]); + continue; + + case 0x0C: // Vibrato. Parameters: Speed, intensity + channel.vibLength = macroPtr[1]; + channel.vibCount = macroPtr[1] / 2; + channel.vibDelta = macroPtr[3]; + // TODO: Perhaps a bug, vibValue could be left uninitialised + if (!channel.portaDelta) { + channel.period = channel.refPeriod; + channel.vibValue = 0; + } + continue; + + case 0x0D: // Add Volume. Parameters: note, addNoteFlag, volume + if (macroPtr[2] == 0xFE) + setNoteMacro(channel, channel.note + macroPtr[1], 0); + channel.volume = channel.relVol * 3 + macroPtr[3]; + continue; + + case 0x0E: // Set Volume. Parameters: note, addNoteFlag, volume + if (macroPtr[2] == 0xFE) + setNoteMacro(channel, channel.note + macroPtr[1], 0); + channel.volume = macroPtr[3]; + continue; + + case 0x0F: // Envelope. Parameters: speed, count, endvol + channel.envDelta = macroPtr[1]; + channel.envCount = channel.envSkip = macroPtr[2]; + channel.envEndVolume = macroPtr[3]; + continue; + + case 0x11: // Add Beginn. Parameters: times, Offset(W) + channel.addBeginLength = channel.addBeginCount = macroPtr[1]; + channel.addBeginDelta = (int16)READ_BE_UINT16(¯oPtr[2]); + channel.sampleStart += channel.addBeginDelta; + Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart)); + continue; + + case 0x12: // Add Length. Parameters: added Length(W) + channel.sampleLen += (int16)READ_BE_UINT16(¯oPtr[2]); + Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen); + continue; + + case 0x14: // Wait key up. Parameters: wait cycles + if (channel.keyUp || channel.macroLoopCount == 0) { + channel.macroLoopCount = 0xFF; + continue; + } else if (channel.macroLoopCount == 0xFF) + channel.macroLoopCount = macroPtr[3]; + --channel.macroLoopCount; + --channel.macroStep; + return; + + case 0x15: // Subroutine. Parameters: MacroIndex, Macrostep(W) + channel.macroReturnOffset = channel.macroOffset; + channel.macroReturnStep = channel.macroStep; + + channel.macroOffset = _resource->macroOffset[macroPtr[1] & (kMaxMacroOffsets - 1)]; + channel.macroStep = READ_BE_UINT16(¯oPtr[2]); + // TODO: MI does some weird stuff there. Figure out which varioables need to be set + continue; + + case 0x16: // Return from Sub. + channel.macroOffset = channel.macroReturnOffset; + channel.macroStep = channel.macroReturnStep; + continue; + + case 0x17: // Set Period. Parameters: Period(W) + channel.refPeriod = READ_BE_UINT16(¯oPtr[2]); + if (!channel.portaDelta) { + channel.period = channel.refPeriod; + //Paula::setChannelPeriod(channel.paulaChannel, channel.period); + } + continue; + + case 0x18: { // Sampleloop. Parameters: Offset from Samplestart(W) + // TODO: MI loads 24 bit, but thats useless? + const uint16 temp = /* ((int8)macroPtr[1] << 16) | */ READ_BE_UINT16(¯oPtr[2]); + if (macroPtr[1] || (temp & 1)) + warning("Tfmx: Problematic value for sampleloop: %06X", (macroPtr[1] << 16) | temp); + channel.sampleStart += temp & 0xFFFE; + channel.sampleLen -= (temp / 2) /* & 0x7FFF */; + Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart)); + Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen); + continue; + } + case 0x19: // Set One-Shot Sample + channel.addBeginLength = 0; + channel.sampleStart = 0; + channel.sampleLen = 1; + Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(0)); + Paula::setChannelSampleLen(channel.paulaChannel, 1); + continue; + + case 0x1A: // Wait on DMA. Parameters: Cycles-1(W) to wait + channel.dmaIntCount = READ_BE_UINT16(¯oPtr[2]) + 1; + channel.macroRun = false; + Paula::setChannelDmaCount(channel.paulaChannel); + break; + +/* case 0x1B: // Random play. Parameters: macro/speed/mode + warnMacroUnimplemented(macroPtr, 0); + continue;*/ + + case 0x1C: // Branch on Note. Parameters: note/macrostep(W) + if (channel.note > macroPtr[1]) + channel.macroStep = READ_BE_UINT16(¯oPtr[2]); + continue; + + case 0x1D: // Branch on Volume. Parameters: volume/macrostep(W) + if (channel.volume > macroPtr[1]) + channel.macroStep = READ_BE_UINT16(¯oPtr[2]); + continue; + +/* case 0x1E: // Addvol+note. Parameters: note/CONST./volume + warnMacroUnimplemented(macroPtr, 0); + continue;*/ + + case 0x1F: // AddPrevNote. Parameters: Note, Finetune(W) + setNoteMacro(channel, channel.prevNote + macroPtr[1], READ_BE_UINT16(¯oPtr[2])); + break; + + case 0x20: // Signal. Parameters: signalnumber, value(W) + if (_playerCtx.numSignals > macroPtr[1]) + _playerCtx.signal[macroPtr[1]] = READ_BE_UINT16(¯oPtr[2]); + continue; + + case 0x21: // Play macro. Parameters: macro, chan, detune + noteCommand(channel.note, macroPtr[1], (channel.relVol << 4) | macroPtr[2], macroPtr[3]); + continue; + + // 0x22 - 0x29 are used by Gem`X + // 0x30 - 0x34 are used by Carribean Disaster + + default: + debug(3, "Tfmx: Macro %02X not supported", macroPtr[0]); + } + if (!deferWait) + return; + } +} + +void Tfmx::advancePatterns() { +startPatterns: + int runningPatterns = 0; + + for (int i = 0; i < kNumChannels; ++i) { + PatternContext &pattern = _patternCtx[i]; + const uint8 pattCmd = pattern.command; + if (pattCmd < 0x90) { // execute Patternstep + ++runningPatterns; + if (!pattern.wait) { + // issue all Steps for this tick + if (patternRun(pattern)) { + // we load the next Trackstep Command and then process all Channels again + if (trackRun(true)) + goto startPatterns; + else + break; + } + + } else + --pattern.wait; + + } else if (pattCmd == 0xFE) { // Stop voice in pattern.expose + pattern.command = 0xFF; + ChannelContext &channel = _channelCtx[pattern.expose & (kNumVoices - 1)]; + if (!channel.sfxLocked) { + haltMacroProgramm(channel); + Paula::disableChannel(channel.paulaChannel); + } + } // else this pattern-Channel is stopped + } + if (_playerCtx.stopWithLastPattern && !runningPatterns) { + stopPaula(); + } +} + +bool Tfmx::patternRun(PatternContext &pattern) { + for (;;) { + const byte *const patternPtr = (const byte *)(getPatternPtr(pattern.offset) + pattern.step); + ++pattern.step; + const byte pattCmd = patternPtr[0]; + + if (pattCmd < 0xF0) { // Playnote + bool doWait = false; + byte noteCmd = pattCmd + pattern.expose; + byte param3 = patternPtr[3]; + if (pattCmd < 0xC0) { // Note + if (pattCmd >= 0x80) { // Wait + pattern.wait = param3; + param3 = 0; + doWait = true; + } + noteCmd &= 0x3F; + } // else Portamento + noteCommand(noteCmd, patternPtr[1], patternPtr[2], param3); + if (doWait) + return false; + + } else { // Patterncommand + switch (pattCmd & 0xF) { + case 0: // End Pattern + Next Trackstep + pattern.command = 0xFF; + --pattern.step; + return true; + + case 1: // Loop Pattern. Parameters: Loopcount, PatternStep(W) + if (pattern.loopCount != 0) { + if (pattern.loopCount == 0xFF) + pattern.loopCount = patternPtr[1]; + pattern.step = READ_BE_UINT16(&patternPtr[2]); + } + --pattern.loopCount; + continue; + + case 2: // Jump. Parameters: PatternIndex, PatternStep(W) + pattern.offset = _resource->patternOffset[patternPtr[1] & (kMaxPatternOffsets - 1)]; + pattern.step = READ_BE_UINT16(&patternPtr[2]); + continue; + + case 3: // Wait. Paramters: ticks to wait + pattern.wait = patternPtr[1]; + return false; + + case 14: // Stop custompattern + // TODO apparently toggles on/off pattern channel 7 + debug(3, "Tfmx: Encountered 'Stop custompattern' command"); + // FT + case 4: // Stop this pattern + pattern.command = 0xFF; + --pattern.step; + // TODO: try figuring out if this was the last Channel? + return false; + + case 5: // Key Up Signal. Paramters: channel + if (!_channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLocked) + _channelCtx[patternPtr[2] & (kNumVoices - 1)].keyUp = true; + continue; + + case 6: // Vibrato. Parameters: length, channel, rate + case 7: // Envelope. Parameters: rate, tempo | channel, endVol + noteCommand(pattCmd, patternPtr[1], patternPtr[2], patternPtr[3]); + continue; + + case 8: // Subroutine. Parameters: pattern, patternstep(W) + pattern.savedOffset = pattern.offset; + pattern.savedStep = pattern.step; + + pattern.offset = _resource->patternOffset[patternPtr[1] & (kMaxPatternOffsets - 1)]; + pattern.step = READ_BE_UINT16(&patternPtr[2]); + continue; + + case 9: // Return from Subroutine + pattern.offset = pattern.savedOffset; + pattern.step = pattern.savedStep; + continue; + + case 10: // fade. Parameters: tempo, endVol + initFadeCommand((uint8)patternPtr[1], (int8)patternPtr[3]); + continue; + + case 11: // play pattern. Parameters: patternCmd, channel, expose + initPattern(_patternCtx[patternPtr[2] & (kNumChannels - 1)], patternPtr[1], patternPtr[3], _resource->patternOffset[patternPtr[1] & (kMaxPatternOffsets - 1)]); + continue; + + case 12: // Lock. Parameters: lockFlag, channel, lockTime + _channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLocked = (patternPtr[1] != 0); + _channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLockTime = patternPtr[3]; + continue; + + case 13: // Cue. Parameters: signalnumber, value(W) + if (_playerCtx.numSignals > patternPtr[1]) + _playerCtx.signal[patternPtr[1]] = READ_BE_UINT16(&patternPtr[2]); + continue; + + case 15: // NOP + continue; + } + } + } +} + +bool Tfmx::trackRun(const bool incStep) { + assert(_playerCtx.song >= 0); + if (incStep) { + // TODO Optionally disable looping + if (_trackCtx.posInd == _trackCtx.stopInd) + _trackCtx.posInd = _trackCtx.startInd; + else + ++_trackCtx.posInd; + } + for (;;) { + const uint16 *const trackData = getTrackPtr(_trackCtx.posInd); + + if (trackData[0] != FROM_BE_16(0xEFFE)) { + // 8 commands for Patterns + for (int i = 0; i < 8; ++i) { + const uint8 *patCmd = (const uint8 *)&trackData[i]; + // First byte is pattern number + const uint8 patNum = patCmd[0]; + // if highest bit is set then keep previous pattern + if (patNum < 0x80) { + initPattern(_patternCtx[i], patNum, patCmd[1], _resource->patternOffset[patNum]); + } else { + _patternCtx[i].command = patNum; + _patternCtx[i].expose = (int8)patCmd[1]; + } + } + return true; + + } else { + // 16 byte Trackstep Command + switch (READ_BE_UINT16(&trackData[1])) { + case 0: // Stop Player. No Parameters + stopPaula(); + return false; + + case 1: // Branch/Loop section of tracksteps. Parameters: branch target, loopcount + if (_trackCtx.loopCount != 0) { + if (_trackCtx.loopCount < 0) + _trackCtx.loopCount = READ_BE_UINT16(&trackData[3]); + _trackCtx.posInd = READ_BE_UINT16(&trackData[2]); + continue; + } + --_trackCtx.loopCount; + break; + + case 2: { // Set Tempo. Parameters: tempo, divisor + _playerCtx.patternCount = _playerCtx.patternSkip = READ_BE_UINT16(&trackData[2]); // tempo + const uint16 temp = READ_BE_UINT16(&trackData[3]); // divisor + + if (!(temp & 0x8000) && (temp & 0x1FF)) + setInterruptFreqUnscaled(temp & 0x1FF); + break; + } + case 4: // Fade. Parameters: tempo, endVol + // load the LSB of the 16bit words + initFadeCommand(((const uint8 *)&trackData[2])[1], ((const int8 *)&trackData[3])[1]); + break; + + case 3: // Unknown, stops player aswell + default: + debug(3, "Tfmx: Unknown Trackstep Command: %02X", READ_BE_UINT16(&trackData[1])); + // MI-Player handles this by stopping the player, we just continue + } + } + + if (_trackCtx.posInd == _trackCtx.stopInd) { + warning("Tfmx: Reached invalid Song-Position"); + return false; + } + ++_trackCtx.posInd; + } +} + +void Tfmx::noteCommand(const uint8 note, const uint8 param1, const uint8 param2, const uint8 param3) { + ChannelContext &channel = _channelCtx[param2 & (kNumVoices - 1)]; + + if (note == 0xFC) { // Lock command + channel.sfxLocked = (param1 != 0); + channel.sfxLockTime = param3; // only 1 byte read! + + } else if (channel.sfxLocked) { // Channel still locked, do nothing + + } else if (note < 0xC0) { // Play Note - Parameters: note, macro, relVol | channel, finetune + + channel.prevNote = channel.note; + channel.note = note; + // channel.macroIndex = param1 & (kMaxMacroOffsets - 1); + channel.macroOffset = _resource->macroOffset[param1 & (kMaxMacroOffsets - 1)]; + channel.relVol = param2 >> 4; + channel.fineTune = (int8)param3; + + // TODO: the point where the channel gets initialised varies with the games, needs more research. + initMacroProgramm(channel); + channel.keyUp = false; // key down = playing a Note + + } else if (note < 0xF0) { // Portamento - Parameters: note, tempo, channel, rate + channel.portaSkip = param1; + channel.portaCount = 1; + if (!channel.portaDelta) + channel.portaValue = channel.refPeriod; + channel.portaDelta = param3; + + channel.note = note & 0x3F; + channel.refPeriod = noteIntervalls[channel.note]; + + } else switch (note) { // Command + + case 0xF5: // Key Up Signal + channel.keyUp = true; + break; + + case 0xF6: // Vibratio - Parameters: length, channel, rate + channel.vibLength = param1 & 0xFE; + channel.vibCount = param1 / 2; + channel.vibDelta = param3; + channel.vibValue = 0; + break; + + case 0xF7: // Envelope - Parameters: rate, tempo | channel, endVol + channel.envDelta = param1; + channel.envCount = channel.envSkip = (param2 >> 4) + 1; + channel.envEndVolume = param3; + break; + } +} + +void Tfmx::initMacroProgramm(ChannelContext &channel) { + channel.macroStep = 0; + channel.macroWait = 0; + channel.macroRun = true; + channel.macroSfxRun = 0; + channel.macroLoopCount = 0xFF; + channel.dmaIntCount = 0; + channel.deferWait = false; + + channel.macroReturnOffset = 0; + channel.macroReturnStep = 0; +} + +void Tfmx::clearEffects(ChannelContext &channel) { + channel.addBeginLength = 0; + channel.envSkip = 0; + channel.vibLength = 0; + channel.portaDelta = 0; +} + +void Tfmx::haltMacroProgramm(ChannelContext &channel) { + channel.macroRun = false; + channel.dmaIntCount = 0; +} + +void Tfmx::unlockMacroChannel(ChannelContext &channel) { + channel.customMacro = 0; + channel.customMacroIndex = 0; + channel.customMacroPrio = 0; + channel.sfxLocked = false; + channel.sfxLockTime = -1; +} + +void Tfmx::initPattern(PatternContext &pattern, uint8 cmd, int8 expose, uint32 offset) { + pattern.command = cmd; + pattern.offset = offset; + pattern.expose = expose; + pattern.step = 0; + pattern.wait = 0; + pattern.loopCount = 0xFF; + + pattern.savedOffset = 0; + pattern.savedStep = 0; +} + +void Tfmx::stopSongImpl(bool stopAudio) { + _playerCtx.song = -1; + for (int i = 0; i < kNumChannels; ++i) { + _patternCtx[i].command = 0xFF; + _patternCtx[i].expose = 0; + } + if (stopAudio) { + stopPaula(); + for (int i = 0; i < kNumVoices; ++i) { + clearEffects(_channelCtx[i]); + unlockMacroChannel(_channelCtx[i]); + haltMacroProgramm(_channelCtx[i]); + _channelCtx[i].note = 0; + _channelCtx[i].volume = 0; + _channelCtx[i].macroSfxRun = -1; + _channelCtx[i].vibValue = 0; + + _channelCtx[i].sampleStart = 0; + _channelCtx[i].sampleLen = 2; + _channelCtx[i].refPeriod = 4; + _channelCtx[i].period = 4; + Paula::disableChannel(i); + } + } +} + +void Tfmx::setNoteMacro(ChannelContext &channel, uint note, int fineTune) { + const uint16 noteInt = noteIntervalls[note & 0x3F]; + const uint16 finetune = (uint16)(fineTune + channel.fineTune + (1 << 8)); + channel.refPeriod = ((uint32)noteInt * finetune >> 8); + if (!channel.portaDelta) + channel.period = channel.refPeriod; +} + +void Tfmx::initFadeCommand(const uint8 fadeTempo, const int8 endVol) { + _playerCtx.fadeCount = _playerCtx.fadeSkip = fadeTempo; + _playerCtx.fadeEndVolume = endVol; + + if (fadeTempo) { + const int diff = _playerCtx.fadeEndVolume - _playerCtx.volume; + _playerCtx.fadeDelta = (diff != 0) ? ((diff > 0) ? 1 : -1) : 0; + } else { + _playerCtx.volume = endVol; + _playerCtx.fadeDelta = 0; + } +} + +void Tfmx::setModuleData(Tfmx &otherPlayer) { + setModuleData(otherPlayer._resource, otherPlayer._resourceSample.sampleData, otherPlayer._resourceSample.sampleLen, false); +} + +bool Tfmx::load(Common::SeekableReadStream &musicData, Common::SeekableReadStream &sampleData, bool autoDelete) { + const MdatResource *mdat = loadMdatFile(musicData); + if (mdat) { + uint32 sampleLen = 0; + const int8 *sampleDat = loadSampleFile(sampleLen, sampleData); + if (sampleDat) { + setModuleData(mdat, sampleDat, sampleLen, autoDelete); + return true; + } + delete[] mdat->mdatAlloc; + delete mdat; + } + return false; +} + +void Tfmx::freeResourceDataImpl() { + if (_deleteResource) { + if (_resource) { + delete[] _resource->mdatAlloc; + delete _resource; + } + delete[] _resourceSample.sampleData; + } + _resource = 0; + _resourceSample.sampleData = 0; + _resourceSample.sampleLen = 0; + _deleteResource = false; +} + +void Tfmx::setModuleData(const MdatResource *resource, const int8 *sampleData, uint32 sampleLen, bool autoDelete) { + Common::StackLock lock(_mutex); + stopSongImpl(true); + freeResourceDataImpl(); + _resource = resource; + _resourceSample.sampleData = sampleData; + _resourceSample.sampleLen = sampleData ? sampleLen : 0; + _deleteResource = autoDelete; +} + +const int8 *Tfmx::loadSampleFile(uint32 &sampleLen, Common::SeekableReadStream &sampleStream) { + sampleLen = 0; + + const int32 sampleSize = sampleStream.size(); + if (sampleSize < 4) { + warning("Tfmx: Cant load Samplefile"); + return false; + } + + int8 *sampleAlloc = new int8[sampleSize]; + if (!sampleAlloc) { + warning("Tfmx: Could not allocate Memory: %dKB", sampleSize / 1024); + return 0; + } + + if (sampleStream.read(sampleAlloc, sampleSize) == (uint32)sampleSize) { + sampleAlloc[0] = sampleAlloc[1] = sampleAlloc[2] = sampleAlloc[3] = 0; + sampleLen = sampleSize; + } else { + delete[] sampleAlloc; + warning("Tfmx: Encountered IO-Error"); + return 0; + } + return sampleAlloc; +} + +const Tfmx::MdatResource *Tfmx::loadMdatFile(Common::SeekableReadStream &musicData) { + bool hasHeader = false; + const int32 mdatSize = musicData.size(); + if (mdatSize >= 0x200) { + byte buf[16] = { 0 }; + // 0x0000: 10 Bytes Header "TFMX-SONG " + musicData.read(buf, 10); + hasHeader = memcmp(buf, "TFMX-SONG ", 10) == 0; + } + + if (!hasHeader) { + warning("Tfmx: File is not a Tfmx Module"); + return 0; + } + + MdatResource *resource = new MdatResource; + + resource->mdatAlloc = 0; + resource->mdatData = 0; + resource->mdatLen = 0; + + // 0x000A: int16 flags + resource->headerFlags = musicData.readUint16BE(); + // 0x000C: int32 ? + // 0x0010: 6*40 Textfield + musicData.skip(4 + 6 * 40); + + /* 0x0100: Songstart x 32*/ + for (int i = 0; i < kNumSubsongs; ++i) + resource->subsong[i].songstart = musicData.readUint16BE(); + /* 0x0140: Songend x 32*/ + for (int i = 0; i < kNumSubsongs; ++i) + resource->subsong[i].songend = musicData.readUint16BE(); + /* 0x0180: Tempo x 32*/ + for (int i = 0; i < kNumSubsongs; ++i) + resource->subsong[i].tempo = musicData.readUint16BE(); + + /* 0x01c0: unused ? */ + musicData.skip(16); + + /* 0x01d0: trackstep, pattern data p, macro data p */ + const uint32 offTrackstep = musicData.readUint32BE(); + uint32 offPatternP, offMacroP; + + // This is how MI`s TFMX-Player tests for unpacked Modules. + if (offTrackstep == 0) { // unpacked File + resource->trackstepOffset = 0x600 + 0x200; + offPatternP = 0x200 + 0x200; + offMacroP = 0x400 + 0x200; + } else { // packed File + resource->trackstepOffset = offTrackstep; + offPatternP = musicData.readUint32BE(); + offMacroP = musicData.readUint32BE(); + } + + // End of basic header, check if everything worked ok + if (musicData.err()) { + warning("Tfmx: Encountered IO-Error"); + delete resource; + return 0; + } + + // TODO: if a File is packed it could have for Ex only 2 Patterns/Macros + // the following loops could then read beyond EOF. + // To correctly handle this it would be necessary to sort the pointers and + // figure out the number of Macros/Patterns + // We could also analyze pointers if they are correct offsets, + // so that accesses can be unchecked later + + // Read in pattern starting offsets + musicData.seek(offPatternP); + for (int i = 0; i < kMaxPatternOffsets; ++i) + resource->patternOffset[i] = musicData.readUint32BE(); + + // use last PatternOffset (stored at 0x5FC in mdat) if unpacked File + // or fixed offset 0x200 if packed + resource->sfxTableOffset = offTrackstep ? 0x200 : resource->patternOffset[127]; + + // Read in macro starting offsets + musicData.seek(offMacroP); + for (int i = 0; i < kMaxMacroOffsets; ++i) + resource->macroOffset[i] = musicData.readUint32BE(); + + // Read in mdat-file + // TODO: we can skip everything thats already stored in the resource-structure. + const int32 mdatOffset = offTrackstep ? 0x200 : 0x600; // 0x200 is very conservative + const uint32 allocSize = (uint32)mdatSize - mdatOffset; + + byte *mdatAlloc = new byte[allocSize]; + if (!mdatAlloc) { + warning("Tfmx: Could not allocate Memory: %dKB", allocSize / 1024); + delete resource; + return 0; + } + musicData.seek(mdatOffset); + if (musicData.read(mdatAlloc, allocSize) == allocSize) { + resource->mdatAlloc = mdatAlloc; + resource->mdatData = mdatAlloc - mdatOffset; + resource->mdatLen = mdatSize; + } else { + delete[] mdatAlloc; + warning("Tfmx: Encountered IO-Error"); + delete resource; + return 0; + } + + return resource; +} + +void Tfmx::doMacro(int note, int macro, int relVol, int finetune, int channelNo) { + assert(0 <= macro && macro < kMaxMacroOffsets); + assert(0 <= note && note < 0xC0); + Common::StackLock lock(_mutex); + + if (!hasResources()) + return; + channelNo &= (kNumVoices - 1); + ChannelContext &channel = _channelCtx[channelNo]; + unlockMacroChannel(channel); + + noteCommand((uint8)note, (uint8)macro, (uint8)((relVol << 4) | channelNo), (uint8)finetune); + startPaula(); +} + +void Tfmx::stopMacroEffect(int channel) { + assert(0 <= channel && channel < kNumVoices); + Common::StackLock lock(_mutex); + unlockMacroChannel(_channelCtx[channel]); + haltMacroProgramm(_channelCtx[channel]); + Paula::disableChannel(_channelCtx[channel].paulaChannel); +} + +void Tfmx::doSong(int songPos, bool stopAudio) { + assert(0 <= songPos && songPos < kNumSubsongs); + Common::StackLock lock(_mutex); + + stopSongImpl(stopAudio); + + if (!hasResources()) + return; + + _trackCtx.loopCount = -1; + _trackCtx.startInd = _trackCtx.posInd = _resource->subsong[songPos].songstart; + _trackCtx.stopInd = _resource->subsong[songPos].songend; + _playerCtx.song = (int8)songPos; + + const bool palFlag = (_resource->headerFlags & 2) != 0; + const uint16 tempo = _resource->subsong[songPos].tempo; + uint16 ciaIntervall; + if (tempo >= 0x10) { + ciaIntervall = (uint16)(kCiaBaseInterval / tempo); + _playerCtx.patternSkip = 0; + } else { + ciaIntervall = palFlag ? (uint16)kPalDefaultCiaVal : (uint16)kNtscDefaultCiaVal; + _playerCtx.patternSkip = tempo; + } + setInterruptFreqUnscaled(ciaIntervall); + Paula::setAudioFilter(true); + + _playerCtx.patternCount = 0; + if (trackRun()) + startPaula(); +} + +int Tfmx::doSfx(uint16 sfxIndex, bool unlockChannel) { + assert(sfxIndex < 128); + Common::StackLock lock(_mutex); + + if (!hasResources()) + return -1; + const byte *sfxEntry = getSfxPtr(sfxIndex); + if (sfxEntry[0] == 0xFB) { + warning("Tfmx: custom patterns are not supported"); + // custompattern + /* const uint8 patCmd = sfxEntry[2]; + const int8 patExp = (int8)sfxEntry[3]; */ + } else { + // custommacro + const byte channelNo = ((_playerCtx.song >= 0) ? sfxEntry[2] : sfxEntry[4]) & (kNumVoices - 1); + const byte priority = sfxEntry[5] & 0x7F; + + ChannelContext &channel = _channelCtx[channelNo]; + if (unlockChannel) + unlockMacroChannel(channel); + + const int16 sfxLocktime = channel.sfxLockTime; + if (priority >= channel.customMacroPrio || sfxLocktime < 0) { + if (sfxIndex != channel.customMacroIndex || sfxLocktime < 0 || (sfxEntry[5] < 0x80)) { + channel.customMacro = READ_UINT32(sfxEntry); // intentionally not "endian-correct" + channel.customMacroPrio = priority; + channel.customMacroIndex = (uint8)sfxIndex; + debug(3, "Tfmx: running Macro %08X on channel %i - priority: %02X", TO_BE_32(channel.customMacro), channelNo, priority); + return channelNo; + } + } + } + return -1; +} + +} // End of namespace Audio + +// some debugging functions +#if 0 +namespace { + +void displayMacroStep(const void * const vptr) { + static const char *tableMacros[] = { + "DMAoff+Resetxx/xx/xx flag/addset/vol ", + "DMAon (start sample at selected begin) ", + "SetBegin xxxxxx sample-startadress", + "SetLen ..xxxx sample-length ", + "Wait ..xxxx count (VBI''s) ", + "Loop xx/xxxx count/step ", + "Cont xx/xxxx macro-number/step ", + "-------------STOP----------------------", + "AddNote xx/xxxx note/detune ", + "SetNote xx/xxxx note/detune ", + "Reset Vibrato-Portamento-Envelope ", + "Portamento xx/../xx count/speed ", + "Vibrato xx/../xx speed/intensity ", + "AddVolume ....xx volume 00-3F ", + "SetVolume ....xx volume 00-3F ", + "Envelope xx/xx/xx speed/count/endvol", + "Loop key up xx/xxxx count/step ", + "AddBegin xx/xxxx count/add to start", + "AddLen ..xxxx add to sample-len ", + "DMAoff stop sample but no clear ", + "Wait key up ....xx count (VBI''s) ", + "Go submacro xx/xxxx macro-number/step ", + "--------Return to old macro------------", + "Setperiod ..xxxx DMA period ", + "Sampleloop ..xxxx relative adress ", + "-------Set one shot sample-------------", + "Wait on DMA ..xxxx count (Wavecycles)", + "Random play xx/xx/xx macro/speed/mode ", + "Splitkey xx/xxxx key/macrostep ", + "Splitvolume xx/xxxx volume/macrostep ", + "Addvol+note xx/fe/xx note/CONST./volume", + "SetPrevNote xx/xxxx note/detune ", + "Signal xx/xxxx signalnumber/value", + "Play macro xx/.x/xx macro/chan/detune ", + "SID setbeg xxxxxx sample-startadress", + "SID setlen xx/xxxx buflen/sourcelen ", + "SID op3 ofs xxxxxx offset ", + "SID op3 frq xx/xxxx speed/amplitude ", + "SID op2 ofs xxxxxx offset ", + "SID op2 frq xx/xxxx speed/amplitude ", + "SID op1 xx/xx/xx speed/amplitude/TC", + "SID stop xx.... flag (1=clear all)" + }; + + const byte *const macroData = (const byte * const)vptr; + if (macroData[0] < ARRAYSIZE(tableMacros)) + debug("%s %02X%02X%02X", tableMacros[macroData[0]], macroData[1], macroData[2], macroData[3]); + else + debug("Unknown Macro #%02X %02X%02X%02X", macroData[0], macroData[1], macroData[2], macroData[3]); +} + +void displayPatternstep(const void * const vptr) { + static const char *tablePatterns[] = { + "End --Next track step--", + "Loop[count / step.w]", + "Cont[patternno./ step.w]", + "Wait[count 00-FF--------", + "Stop--Stop this pattern-", + "Kup^-Set key up/channel]", + "Vibr[speed / rate.b]", + "Enve[speed /endvolume.b]", + "GsPt[patternno./ step.w]", + "RoPt-Return old pattern-", + "Fade[speed /endvolume.b]", + "PPat[patt./track+transp]", + "Lock---------ch./time.b]", + "Cue [number.b/ value.w]", + "Stop-Stop custompattern-", + "NOP!-no operation-------" + }; + + const byte * const patData = (const byte * const)vptr; + const byte command = patData[0]; + if (command < 0xF0) { // Playnote + const byte flags = command >> 6; // 0-1 means note+detune, 2 means wait, 3 means portamento? + const char *flagsSt[] = { "Note ", "Note ", "Wait ", "Porta" }; + debug("%s %02X%02X%02X%02X", flagsSt[flags], patData[0], patData[1], patData[2], patData[3]); + } else + debug("%s %02X%02X%02X",tablePatterns[command & 0xF], patData[1], patData[2], patData[3]); +} + +} // End of anonymous namespace +#endif + +#endif // #if defined(SOUND_MODS_TFMX_H) diff --git a/audio/mods/tfmx.h b/audio/mods/tfmx.h new file mode 100644 index 0000000000..1930487eb8 --- /dev/null +++ b/audio/mods/tfmx.h @@ -0,0 +1,284 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// see if all engines using this class are DISABLED +#if !defined(ENABLE_SCUMM) + +// normal Header Guard +#elif !defined(SOUND_MODS_TFMX_H) +#define SOUND_MODS_TFMX_H + +#include "audio/mods/paula.h" + +namespace Audio { + +class Tfmx : public Paula { +public: + Tfmx(int rate, bool stereo); + virtual ~Tfmx(); + + /** + * Stops a playing Song (but leaves macros running) and optionally also stops the player + * + * @param stopAudio stops player and audio output + * @param dataSize number of bytes to be written + * @return the number of bytes which were actually written. + */ + void stopSong(bool stopAudio = true) { Common::StackLock lock(_mutex); stopSongImpl(stopAudio); } + /** + * Stops currently playing Song (if any) and cues up a new one. + * if stopAudio is specified, the player gets reset before starting the new song + * + * @param songPos index of Song to play + * @param stopAudio stops player and audio output + * @param dataSize number of bytes to be written + * @return the number of bytes which were actually written. + */ + void doSong(int songPos, bool stopAudio = false); + /** + * plays an effect from the sfx-table, does not start audio-playback. + * + * @param sfxIndex index of effect to play + * @param unlockChannel overwrite higher priority effects + * @return index of the channel which now queued up the effect. + * -1 in case the effect couldnt be queued up + */ + int doSfx(uint16 sfxIndex, bool unlockChannel = false); + /** + * stop a running macro channel + * + * @param channel index of effect to stop + */ + void stopMacroEffect(int channel); + + void doMacro(int note, int macro, int relVol = 0, int finetune = 0, int channelNo = 0); + int getTicks() const { return _playerCtx.tickCount; } + int getSongIndex() const { return _playerCtx.song; } + void setSignalPtr(uint16 *ptr, uint16 numSignals) { _playerCtx.signal = ptr; _playerCtx.numSignals = numSignals; } + void freeResources() { _deleteResource = true; freeResourceDataImpl(); } + bool load(Common::SeekableReadStream &musicData, Common::SeekableReadStream &sampleData, bool autoDelete = true); + void setModuleData(Tfmx &otherPlayer); + +protected: + void interrupt(); + +private: + enum { kPalDefaultCiaVal = 11822, kNtscDefaultCiaVal = 14320, kCiaBaseInterval = 0x1B51F8 }; + enum { kNumVoices = 4, kNumChannels = 8, kNumSubsongs = 32, kMaxPatternOffsets = 128, kMaxMacroOffsets = 128 }; + + struct MdatResource { + const byte *mdatAlloc; ///< allocated Block of Memory + const byte *mdatData; ///< Start of mdat-File, might point before mdatAlloc to correct Offset + uint32 mdatLen; + + uint16 headerFlags; +// uint32 headerUnknown; +// char textField[6 * 40]; + + struct Subsong { + uint16 songstart; ///< Index in Trackstep-Table + uint16 songend; ///< Last index in Trackstep-Table + uint16 tempo; + } subsong[kNumSubsongs]; + + uint32 trackstepOffset; ///< Offset in mdat + uint32 sfxTableOffset; + + uint32 patternOffset[kMaxPatternOffsets]; ///< Offset in mdat + uint32 macroOffset[kMaxMacroOffsets]; ///< Offset in mdat + + void boundaryCheck(const void *address, size_t accessLen = 1) const { + assert(mdatAlloc <= address && (const byte *)address + accessLen <= (const byte *)mdatData + mdatLen); + } + } const *_resource; + + struct SampleResource { + const int8 *sampleData; ///< The whole sample-File + uint32 sampleLen; + + void boundaryCheck(const void *address, size_t accessLen = 2) const { + assert(sampleData <= address && (const byte *)address + accessLen <= (const byte *)sampleData + sampleLen); + } + } _resourceSample; + + bool _deleteResource; + + bool hasResources() { + return _resource && _resource->mdatLen && _resourceSample.sampleLen; + } + + struct ChannelContext { + byte paulaChannel; + +// byte macroIndex; + uint16 macroWait; + uint32 macroOffset; + uint32 macroReturnOffset; + uint16 macroStep; + uint16 macroReturnStep; + uint8 macroLoopCount; + bool macroRun; + int8 macroSfxRun; ///< values are the folowing: -1 macro disabled, 0 macro init, 1 macro running + + uint32 customMacro; + uint8 customMacroIndex; + uint8 customMacroPrio; + + bool sfxLocked; + int16 sfxLockTime; + bool keyUp; + + bool deferWait; + uint16 dmaIntCount; + + uint32 sampleStart; + uint16 sampleLen; + uint16 refPeriod; + uint16 period; + + int8 volume; + uint8 relVol; + uint8 note; + uint8 prevNote; + int16 fineTune; // always a signextended byte + + uint8 portaSkip; + uint8 portaCount; + uint16 portaDelta; + uint16 portaValue; + + uint8 envSkip; + uint8 envCount; + uint8 envDelta; + int8 envEndVolume; + + uint8 vibLength; + uint8 vibCount; + int16 vibValue; + int8 vibDelta; + + uint8 addBeginLength; + uint8 addBeginCount; + int32 addBeginDelta; + } _channelCtx[kNumVoices]; + + struct PatternContext { + uint32 offset; // patternStart, Offset from mdat + uint32 savedOffset; // for subroutine calls + uint16 step; // distance from patternStart + uint16 savedStep; + + uint8 command; + int8 expose; + uint8 loopCount; + uint8 wait; ///< how many ticks to wait before next Command + } _patternCtx[kNumChannels]; + + struct TrackStepContext { + uint16 startInd; + uint16 stopInd; + uint16 posInd; + int16 loopCount; + } _trackCtx; + + struct PlayerContext { + int8 song; ///< >= 0 if Song is running (means process Patterns) + + uint16 patternCount; + uint16 patternSkip; ///< skip that amount of CIA-Interrupts + + int8 volume; ///< Master Volume + + uint8 fadeSkip; + uint8 fadeCount; + int8 fadeEndVolume; + int8 fadeDelta; + + int tickCount; + + uint16 *signal; + uint16 numSignals; + + bool stopWithLastPattern; ///< hack to automatically stop the whole player if no Pattern is running + } _playerCtx; + + const byte *getSfxPtr(uint16 index = 0) const { + const byte *sfxPtr = (const byte *)(_resource->mdatData + _resource->sfxTableOffset + index * 8); + + _resource->boundaryCheck(sfxPtr, 8); + return sfxPtr; + } + + const uint16 *getTrackPtr(uint16 trackstep = 0) const { + const uint16 *trackData = (const uint16 *)(_resource->mdatData + _resource->trackstepOffset + 16 * trackstep); + + _resource->boundaryCheck(trackData, 16); + return trackData; + } + + const uint32 *getPatternPtr(uint32 offset) const { + const uint32 *pattData = (const uint32 *)(_resource->mdatData + offset); + + _resource->boundaryCheck(pattData, 4); + return pattData; + } + + const uint32 *getMacroPtr(uint32 offset) const { + const uint32 *macroData = (const uint32 *)(_resource->mdatData + offset); + + _resource->boundaryCheck(macroData, 4); + return macroData; + } + + const int8 *getSamplePtr(const uint32 offset) const { + const int8 *sample = _resourceSample.sampleData + offset; + + _resourceSample.boundaryCheck(sample, 2); + return sample; + } + + static inline void initMacroProgramm(ChannelContext &channel); + static inline void clearEffects(ChannelContext &channel); + static inline void haltMacroProgramm(ChannelContext &channel); + static inline void unlockMacroChannel(ChannelContext &channel); + static inline void initPattern(PatternContext &pattern, uint8 cmd, int8 expose, uint32 offset); + void stopSongImpl(bool stopAudio = true); + static inline void setNoteMacro(ChannelContext &channel, uint note, int fineTune); + void initFadeCommand(const uint8 fadeTempo, const int8 endVol); + void setModuleData(const MdatResource *resource, const int8 *sampleData, uint32 sampleLen, bool autoDelete = true); + static const MdatResource *loadMdatFile(Common::SeekableReadStream &musicData); + static const int8 *loadSampleFile(uint32 &sampleLen, Common::SeekableReadStream &sampleStream); + void freeResourceDataImpl(); + void effects(ChannelContext &channel); + void macroRun(ChannelContext &channel); + void advancePatterns(); + bool patternRun(PatternContext &pattern); + bool trackRun(bool incStep = false); + void noteCommand(uint8 note, uint8 param1, uint8 param2, uint8 param3); +}; + +} // End of namespace Audio + +#endif // !defined(SOUND_MODS_TFMX_H) diff --git a/audio/module.mk b/audio/module.mk new file mode 100644 index 0000000000..5b93c80d57 --- /dev/null +++ b/audio/module.mk @@ -0,0 +1,61 @@ +MODULE := audio + +MODULE_OBJS := \ + audiostream.o \ + fmopl.o \ + mididrv.o \ + midiparser_smf.o \ + midiparser_xmidi.o \ + midiparser.o \ + mixer.o \ + mpu401.o \ + musicplugin.o \ + null.o \ + timestamp.o \ + decoders/adpcm.o \ + decoders/aiff.o \ + decoders/flac.o \ + decoders/iff_sound.o \ + decoders/mac_snd.o \ + decoders/mp3.o \ + decoders/raw.o \ + decoders/vag.o \ + decoders/voc.o \ + decoders/vorbis.o \ + decoders/wave.o \ + mods/infogrames.o \ + mods/maxtrax.o \ + mods/module.o \ + mods/protracker.o \ + mods/paula.o \ + mods/rjp1.o \ + mods/soundfx.o \ + mods/tfmx.o \ + softsynth/adlib.o \ + softsynth/cms.o \ + softsynth/opl/dbopl.o \ + softsynth/opl/dosbox.o \ + softsynth/opl/mame.o \ + softsynth/fmtowns_pc98/towns_audio.o \ + softsynth/fmtowns_pc98/towns_euphony.o \ + softsynth/fmtowns_pc98/towns_pc98_driver.o \ + softsynth/fmtowns_pc98/towns_pc98_fmsynth.o \ + softsynth/appleiigs.o \ + softsynth/ym2612.o \ + softsynth/fluidsynth.o \ + softsynth/mt32.o \ + softsynth/pcspk.o \ + softsynth/sid.o \ + softsynth/wave6581.o + +ifndef USE_ARM_SOUND_ASM +MODULE_OBJS += \ + rate.o +else +MODULE_OBJS += \ + rate_arm.o \ + rate_arm_asm.o +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/audio/mpu401.cpp b/audio/mpu401.cpp new file mode 100644 index 0000000000..4f62de930c --- /dev/null +++ b/audio/mpu401.cpp @@ -0,0 +1,145 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "audio/mpu401.h" +#include "common/system.h" +#include "common/timer.h" +#include "common/util.h" // for ARRAYSIZE + +void MidiChannel_MPU401::init(MidiDriver *owner, byte channel) { + _owner = owner; + _channel = channel; + _allocated = false; +} + +bool MidiChannel_MPU401::allocate() { + if (_allocated) + return false; + return (_allocated = true); +} + +MidiDriver *MidiChannel_MPU401::device() { + return _owner; +} + +void MidiChannel_MPU401::send(uint32 b) { + _owner->send((b & 0xFFFFFFF0) | (_channel & 0xF)); +} + +void MidiChannel_MPU401::noteOff(byte note) { + _owner->send(note << 8 | 0x80 | _channel); +} + +void MidiChannel_MPU401::noteOn(byte note, byte velocity) { + _owner->send(velocity << 16 | note << 8 | 0x90 | _channel); +} + +void MidiChannel_MPU401::programChange(byte program) { + _owner->send(program << 8 | 0xC0 | _channel); +} + +void MidiChannel_MPU401::pitchBend(int16 bend) { + _owner->send((((bend + 0x2000) >> 7) & 0x7F) << 16 | ((bend + 0x2000) & 0x7F) << 8 | 0xE0 | _channel); +} + +void MidiChannel_MPU401::controlChange(byte control, byte value) { + _owner->send(value << 16 | control << 8 | 0xB0 | _channel); +} + +void MidiChannel_MPU401::pitchBendFactor(byte value) { + _owner->setPitchBendRange(_channel, value); +} + +void MidiChannel_MPU401::sysEx_customInstrument(uint32 type, const byte *instr) { + _owner->sysEx_customInstrument(_channel, type, instr); +} + +const char *MidiDriver::getErrorName(int error_code) { + static const char *const midi_errors[] = { + "No error", + "Cannot connect", + "Streaming not available", + "Device not available", + "Driver already open" + }; + + if ((uint)error_code >= ARRAYSIZE(midi_errors)) + return "Unknown Error"; + return midi_errors[error_code]; +} + +MidiDriver_MPU401::MidiDriver_MPU401() : + MidiDriver(), + _timer_proc(0), + _channel_mask(0xFFFF) // Permit all 16 channels by default +{ + + uint i; + for (i = 0; i < ARRAYSIZE(_midi_channels); ++i) { + _midi_channels[i].init(this, i); + } +} + +void MidiDriver_MPU401::close() { + if (_timer_proc) + g_system->getTimerManager()->removeTimerProc(_timer_proc); + _timer_proc = 0; + for (int i = 0; i < 16; ++i) + send(0x7B << 8 | 0xB0 | i); +} + +uint32 MidiDriver_MPU401::property(int prop, uint32 param) { + switch (prop) { + case PROP_CHANNEL_MASK: + _channel_mask = param & 0xFFFF; + return 1; + } + + return 0; +} + +MidiChannel *MidiDriver_MPU401::allocateChannel() { + MidiChannel_MPU401 *chan; + uint i; + + for (i = 0; i < ARRAYSIZE(_midi_channels); ++i) { + if (i == 9 || !(_channel_mask & (1 << i))) + continue; + chan = &_midi_channels[i]; + if (chan->allocate()) { + return chan; + } + } + return NULL; +} + +void MidiDriver_MPU401::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + if (!_timer_proc || !timer_proc) { + if (_timer_proc) + g_system->getTimerManager()->removeTimerProc(_timer_proc); + _timer_proc = timer_proc; + if (timer_proc) + g_system->getTimerManager()->installTimerProc(timer_proc, 10000, timer_param); + } +} diff --git a/audio/mpu401.h b/audio/mpu401.h new file mode 100644 index 0000000000..070eaf636a --- /dev/null +++ b/audio/mpu401.h @@ -0,0 +1,92 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SOUND_MPU401_H +#define SOUND_MPU401_H + +#include "audio/mididrv.h" + +//////////////////////////////////////// +// +// Common MPU401 implementation methods +// +//////////////////////////////////////// + +class MidiDriver_MPU401; + +class MidiChannel_MPU401 : public MidiChannel { + +private: + MidiDriver *_owner; + bool _allocated; + byte _channel; + +public: + MidiDriver *device(); + byte getNumber() { return _channel; } + void release() { _allocated = false; } + + void send(uint32 b); + + // Regular messages + void noteOff(byte note); + void noteOn(byte note, byte velocity); + void programChange(byte program); + void pitchBend(int16 bend); + + // Control Change messages + void controlChange(byte control, byte value); + void pitchBendFactor(byte value); + + // SysEx messages + void sysEx_customInstrument(uint32 type, const byte *instr); + + // Only to be called by the owner + void init(MidiDriver *owner, byte channel); + bool allocate(); +}; + + + +class MidiDriver_MPU401 : public MidiDriver { +private: + MidiChannel_MPU401 _midi_channels[16]; + Common::TimerManager::TimerProc _timer_proc; + uint16 _channel_mask; + +public: + MidiDriver_MPU401(); + + virtual void close(); + void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc); + uint32 getBaseTempo(void) { return 10000; } + uint32 property(int prop, uint32 param); + + MidiChannel *allocateChannel(); + MidiChannel *getPercussionChannel() { return &_midi_channels[9]; } +}; + + +#endif diff --git a/audio/musicplugin.cpp b/audio/musicplugin.cpp new file mode 100644 index 0000000000..eb28d2f4c9 --- /dev/null +++ b/audio/musicplugin.cpp @@ -0,0 +1,64 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/musicplugin.h" +#include "common/hash-str.h" +#include "common/translation.h" + +MusicDevice::MusicDevice(MusicPluginObject const *musicPlugin, Common::String name, MusicType mt) : + _musicDriverName(_(musicPlugin->getName())), _musicDriverId(musicPlugin->getId()), + _name(_(name)), _type(mt) { +} + +Common::String MusicDevice::getCompleteName() { + Common::String name; + + if (_name.empty()) { + // Default device, just show the driver name + name = _musicDriverName; + } else { + // Show both device and driver names + name = _name; + name += " ["; + name += _musicDriverName; + name += "]"; + } + + return name; +} + +Common::String MusicDevice::getCompleteId() { + Common::String id = _musicDriverId; + if (!_name.empty()) { + id += "_"; + id += _name; + } + + return id; +} + +MidiDriver::DeviceHandle MusicDevice::getHandle() { + return (MidiDriver::DeviceHandle)Common::hashit(getCompleteId().c_str()); +} diff --git a/audio/musicplugin.h b/audio/musicplugin.h new file mode 100644 index 0000000000..da0e0ff816 --- /dev/null +++ b/audio/musicplugin.h @@ -0,0 +1,125 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef SOUND_MUSICPLUGIN_H +#define SOUND_MUSICPLUGIN_H + +#include "base/plugins.h" +#include "audio/mididrv.h" +#include "common/list.h" + +class MusicPluginObject; + +/** + * Description of a Music device. Used to list the devices a Music driver + * can manage and their capabilities. + * A device with an empty name means the default device. + */ +class MusicDevice { +public: + MusicDevice(MusicPluginObject const *musicPlugin, Common::String name, MusicType mt); + + Common::String &getName() { return _name; } + Common::String &getMusicDriverName() { return _musicDriverName; } + Common::String &getMusicDriverId() { return _musicDriverId; } + MusicType getMusicType() { return _type; } + + /** + * Returns a user readable string that contains the name of the current + * device name (if it isn't the default one) and the name of the driver. + */ + Common::String getCompleteName(); + + /** + * Returns a user readable string that contains the name of the current + * device name (if it isn't the default one) and the id of the driver. + */ + Common::String getCompleteId(); + + MidiDriver::DeviceHandle getHandle(); + +private: + Common::String _name; + Common::String _musicDriverName; + Common::String _musicDriverId; + MusicType _type; +}; + +/** List of music devices. */ +typedef Common::List MusicDevices; + +/** + * A MusicPluginObject is essentially a factory for MidiDriver instances with + * the added ability of listing the available devices and their capabilities. + */ +class MusicPluginObject : public PluginObject { +public: + virtual ~MusicPluginObject() {} + + /** + * Returns a unique string identifier which will be used to save the + * selected MIDI driver to the config file. + */ + virtual const char *getId() const = 0; + + /** + * Returns a list of the available devices. + */ + virtual MusicDevices getDevices() const = 0; + + /** + * Tries to instantiate a MIDI Driver instance based on the device + * previously detected via MidiDriver::detectDevice() + * + * @param mididriver Pointer to a pointer which the MusicPluginObject sets + * to the newly create MidiDriver, or 0 in case of an error + * + * @param dev Pointer to a device to be used then creating the driver instance. + * Default value of zero for driver types without devices. + * + * @return a Common::Error describing the error which occurred, or kNoError + */ + virtual Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const = 0; +}; + + +// Music plugins + +typedef PluginSubclass MusicPlugin; + +/** + * Singleton class which manages all Music plugins. + */ +class MusicManager : public Common::Singleton { +private: + friend class Common::Singleton; + +public: + const MusicPlugin::List &getPlugins() const; +}; + +/** Convenience shortcut for accessing the Music manager. */ +#define MusicMan MusicManager::instance() + +#endif diff --git a/audio/null.cpp b/audio/null.cpp new file mode 100644 index 0000000000..152f5da03e --- /dev/null +++ b/audio/null.cpp @@ -0,0 +1,62 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "audio/null.h" + +Common::Error NullMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const { + *mididriver = new MidiDriver_NULL(); + + return Common::kNoError; +} + +MusicDevices NullMusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_NULL)); + return devices; +} + +class AutoMusicPlugin : public NullMusicPlugin { +public: + const char *getName() const { + return _s(""); + } + + const char *getId() const { + return "auto"; + } + MusicDevices getDevices() const; +}; + +MusicDevices AutoMusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_AUTO)); + return devices; +} + +//#if PLUGIN_ENABLED_DYNAMIC(NULL) + //REGISTER_PLUGIN_DYNAMIC(NULL, PLUGIN_TYPE_MUSIC, NullMusicPlugin); +//#else + REGISTER_PLUGIN_STATIC(AUTO, PLUGIN_TYPE_MUSIC, AutoMusicPlugin); + REGISTER_PLUGIN_STATIC(NULL, PLUGIN_TYPE_MUSIC, NullMusicPlugin); +//#endif diff --git a/audio/null.h b/audio/null.h new file mode 100644 index 0000000000..5df7493a06 --- /dev/null +++ b/audio/null.h @@ -0,0 +1,56 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef SOUND_NULL_H +#define SOUND_NULL_H + +#include "audio/musicplugin.h" +#include "audio/mpu401.h" +#include "common/translation.h" + +/* NULL driver */ +class MidiDriver_NULL : public MidiDriver_MPU401 { +public: + int open() { return 0; } + void send(uint32 b) { } +}; + + +// Plugin interface + +class NullMusicPlugin : public MusicPluginObject { +public: + virtual const char *getName() const { + return _s("No music"); + } + + virtual const char *getId() const { + return "null"; + } + + virtual MusicDevices getDevices() const; + Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const; +}; + +#endif diff --git a/audio/rate.cpp b/audio/rate.cpp new file mode 100644 index 0000000000..1fcf0ade2e --- /dev/null +++ b/audio/rate.cpp @@ -0,0 +1,358 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * The code in this file is based on code with Copyright 1998 Fabrice Bellard + * Fabrice original code is part of SoX (http://sox.sourceforge.net). + * Max Horn adapted that code to the needs of ScummVM and rewrote it partial, + * in the process removing any use of floating point arithmetic. Various other + * improvements over the original code were made. + */ + +#include "audio/audiostream.h" +#include "audio/rate.h" +#include "audio/mixer.h" +#include "common/frac.h" +#include "common/util.h" + +namespace Audio { + + +/** + * The size of the intermediate input cache. Bigger values may increase + * performance, but only until some point (depends largely on cache size, + * target processor and various other factors), at which it will decrease + * again. + */ +#define INTERMEDIATE_BUFFER_SIZE 512 + + +/** + * Audio rate converter based on simple resampling. Used when no + * interpolation is required. + * + * Limited to sampling frequency <= 65535 Hz. + */ +template +class SimpleRateConverter : public RateConverter { +protected: + st_sample_t inBuf[INTERMEDIATE_BUFFER_SIZE]; + const st_sample_t *inPtr; + int inLen; + + /** position of how far output is ahead of input */ + /** Holds what would have been opos-ipos */ + long opos; + + /** fractional position increment in the output stream */ + long opos_inc; + +public: + SimpleRateConverter(st_rate_t inrate, st_rate_t outrate); + int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r); + int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { + return ST_SUCCESS; + } +}; + + +/* + * Prepare processing. + */ +template +SimpleRateConverter::SimpleRateConverter(st_rate_t inrate, st_rate_t outrate) { + if ((inrate % outrate) != 0) { + error("Input rate must be a multiple of output rate to use rate effect"); + } + + if (inrate >= 65536 || outrate >= 65536) { + error("rate effect can only handle rates < 65536"); + } + + opos = 1; + + /* increment */ + opos_inc = inrate / outrate; + + inLen = 0; +} + +/* + * Processed signed long samples from ibuf to obuf. + * Return number of sample pairs processed. + */ +template +int SimpleRateConverter::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { + st_sample_t *ostart, *oend; + + ostart = obuf; + oend = obuf + osamp * 2; + + while (obuf < oend) { + + // read enough input samples so that opos >= 0 + do { + // Check if we have to refill the buffer + if (inLen == 0) { + inPtr = inBuf; + inLen = input.readBuffer(inBuf, ARRAYSIZE(inBuf)); + if (inLen <= 0) + return (obuf - ostart) / 2; + } + inLen -= (stereo ? 2 : 1); + opos--; + if (opos >= 0) { + inPtr += (stereo ? 2 : 1); + } + } while (opos >= 0); + + st_sample_t out0, out1; + out0 = *inPtr++; + out1 = (stereo ? *inPtr++ : out0); + + // Increment output position + opos += opos_inc; + + // output left channel + clampedAdd(obuf[reverseStereo ], (out0 * (int)vol_l) / Audio::Mixer::kMaxMixerVolume); + + // output right channel + clampedAdd(obuf[reverseStereo ^ 1], (out1 * (int)vol_r) / Audio::Mixer::kMaxMixerVolume); + + obuf += 2; + } + return (obuf - ostart) / 2; +} + +/** + * Audio rate converter based on simple linear Interpolation. + * + * The use of fractional increment allows us to use no buffer. It + * avoid the problems at the end of the buffer we had with the old + * method which stored a possibly big buffer of size + * lcm(in_rate,out_rate). + * + * Limited to sampling frequency <= 65535 Hz. + */ + +template +class LinearRateConverter : public RateConverter { +protected: + st_sample_t inBuf[INTERMEDIATE_BUFFER_SIZE]; + const st_sample_t *inPtr; + int inLen; + + /** fractional position of the output stream in input stream unit */ + frac_t opos; + + /** fractional position increment in the output stream */ + frac_t opos_inc; + + /** last sample(s) in the input stream (left/right channel) */ + st_sample_t ilast0, ilast1; + /** current sample(s) in the input stream (left/right channel) */ + st_sample_t icur0, icur1; + +public: + LinearRateConverter(st_rate_t inrate, st_rate_t outrate); + int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r); + int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { + return ST_SUCCESS; + } +}; + + +/* + * Prepare processing. + */ +template +LinearRateConverter::LinearRateConverter(st_rate_t inrate, st_rate_t outrate) { + if (inrate >= 65536 || outrate >= 65536) { + error("rate effect can only handle rates < 65536"); + } + + opos = FRAC_ONE; + + // Compute the linear interpolation increment. + // This will overflow if inrate >= 2^16, and underflow if outrate >= 2^16. + // Also, if the quotient of the two rate becomes too small / too big, that + // would cause problems, but since we rarely scale from 1 to 65536 Hz or vice + // versa, I think we can live with that limitation ;-). + opos_inc = (inrate << FRAC_BITS) / outrate; + + ilast0 = ilast1 = 0; + icur0 = icur1 = 0; + + inLen = 0; +} + +/* + * Processed signed long samples from ibuf to obuf. + * Return number of sample pairs processed. + */ +template +int LinearRateConverter::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { + st_sample_t *ostart, *oend; + + ostart = obuf; + oend = obuf + osamp * 2; + + while (obuf < oend) { + + // read enough input samples so that opos < 0 + while ((frac_t)FRAC_ONE <= opos) { + // Check if we have to refill the buffer + if (inLen == 0) { + inPtr = inBuf; + inLen = input.readBuffer(inBuf, ARRAYSIZE(inBuf)); + if (inLen <= 0) + return (obuf - ostart) / 2; + } + inLen -= (stereo ? 2 : 1); + ilast0 = icur0; + icur0 = *inPtr++; + if (stereo) { + ilast1 = icur1; + icur1 = *inPtr++; + } + opos -= FRAC_ONE; + } + + // Loop as long as the outpos trails behind, and as long as there is + // still space in the output buffer. + while (opos < (frac_t)FRAC_ONE && obuf < oend) { + // interpolate + st_sample_t out0, out1; + out0 = (st_sample_t)(ilast0 + (((icur0 - ilast0) * opos + FRAC_HALF) >> FRAC_BITS)); + out1 = (stereo ? + (st_sample_t)(ilast1 + (((icur1 - ilast1) * opos + FRAC_HALF) >> FRAC_BITS)) : + out0); + + // output left channel + clampedAdd(obuf[reverseStereo ], (out0 * (int)vol_l) / Audio::Mixer::kMaxMixerVolume); + + // output right channel + clampedAdd(obuf[reverseStereo ^ 1], (out1 * (int)vol_r) / Audio::Mixer::kMaxMixerVolume); + + obuf += 2; + + // Increment output position + opos += opos_inc; + } + } + return (obuf - ostart) / 2; +} + + +#pragma mark - + + +/** + * Simple audio rate converter for the case that the inrate equals the outrate. + */ +template +class CopyRateConverter : public RateConverter { + st_sample_t *_buffer; + st_size_t _bufferSize; +public: + CopyRateConverter() : _buffer(0), _bufferSize(0) {} + ~CopyRateConverter() { + free(_buffer); + } + + virtual int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { + assert(input.isStereo() == stereo); + + st_sample_t *ptr; + st_size_t len; + + st_sample_t *ostart = obuf; + + if (stereo) + osamp *= 2; + + // Reallocate temp buffer, if necessary + if (osamp > _bufferSize) { + free(_buffer); + _buffer = (st_sample_t *)malloc(osamp * 2); + _bufferSize = osamp; + } + + // Read up to 'osamp' samples into our temporary buffer + len = input.readBuffer(_buffer, osamp); + + // Mix the data into the output buffer + ptr = _buffer; + for (; len > 0; len -= (stereo ? 2 : 1)) { + st_sample_t out0, out1; + out0 = *ptr++; + out1 = (stereo ? *ptr++ : out0); + + // output left channel + clampedAdd(obuf[reverseStereo ], (out0 * (int)vol_l) / Audio::Mixer::kMaxMixerVolume); + + // output right channel + clampedAdd(obuf[reverseStereo ^ 1], (out1 * (int)vol_r) / Audio::Mixer::kMaxMixerVolume); + + obuf += 2; + } + return (obuf - ostart) / 2; + } + + virtual int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { + return ST_SUCCESS; + } +}; + + +#pragma mark - + +template +RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate) { + if (inrate != outrate) { + if ((inrate % outrate) == 0) { + return new SimpleRateConverter(inrate, outrate); + } else { + return new LinearRateConverter(inrate, outrate); + } + } else { + return new CopyRateConverter(); + } +} + +/** + * Create and return a RateConverter object for the specified input and output rates. + */ +RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate, bool stereo, bool reverseStereo) { + if (stereo) { + if (reverseStereo) + return makeRateConverter(inrate, outrate); + else + return makeRateConverter(inrate, outrate); + } else + return makeRateConverter(inrate, outrate); +} + +} // End of namespace Audio diff --git a/audio/rate.h b/audio/rate.h new file mode 100644 index 0000000000..fb231e4c4a --- /dev/null +++ b/audio/rate.h @@ -0,0 +1,90 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SOUND_RATE_H +#define SOUND_RATE_H + +#include "common/scummsys.h" +#include "engines/engine.h" + +class AudioStream; + + +namespace Audio { + +typedef int16 st_sample_t; +typedef uint16 st_volume_t; +typedef uint32 st_size_t; +typedef uint32 st_rate_t; + +/* Minimum and maximum values a sample can hold. */ +enum { + ST_SAMPLE_MAX = 0x7fffL, + ST_SAMPLE_MIN = (-ST_SAMPLE_MAX - 1L) +}; + +enum { + ST_EOF = -1, + ST_SUCCESS = 0 +}; + +static inline void clampedAdd(int16& a, int b) { + register int val; +#ifdef OUTPUT_UNSIGNED_AUDIO + val = (a ^ 0x8000) + b; +#else + val = a + b; +#endif + + if (val > ST_SAMPLE_MAX) + val = ST_SAMPLE_MAX; + else if (val < ST_SAMPLE_MIN) + val = ST_SAMPLE_MIN; + +#ifdef OUTPUT_UNSIGNED_AUDIO + a = ((int16)val) ^ 0x8000; +#else + a = val; +#endif +} + +class RateConverter { +public: + RateConverter() {} + virtual ~RateConverter() {} + + /** + * @return Number of sample pairs written into the buffer. + */ + virtual int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) = 0; + + virtual int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) = 0; +}; + +RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate, bool stereo, bool reverseStereo = false); + +} // End of namespace Audio + +#endif diff --git a/audio/rate_arm.cpp b/audio/rate_arm.cpp new file mode 100644 index 0000000000..41944ef698 --- /dev/null +++ b/audio/rate_arm.cpp @@ -0,0 +1,465 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * The code in this file, together with the rate_arm_asm.s file offers + * an ARM optimised version of the code in rate.cpp. The operation of this + * code should be identical to that of rate.cpp, but faster. The heavy + * lifting is done in the assembler file. + * + * To be as portable as possible we implement the core routines with C + * linkage in assembly, and implement the C++ routines that call into + * the C here. The C++ symbol mangling varies wildly between compilers, + * so this is the simplest way to ensure that the C/C++ combination should + * work on as many ARM based platforms as possible. + * + * Essentially the algorithm herein is the same as that in rate.cpp, so + * anyone seeking to understand this should attempt to understand that + * first. That code was based in turn on code with Copyright 1998 Fabrice + * Bellard - part of SoX (http://sox.sourceforge.net). + * Max Horn adapted that code to the needs of ScummVM and partially rewrote + * it, in the process removing any use of floating point arithmetic. Various + * other improvments over the original code were made. + */ + +#include "audio/audiostream.h" +#include "audio/rate.h" +#include "audio/mixer.h" +#include "common/util.h" + +//#define DEBUG_RATECONV + +namespace Audio { + +/** + * The precision of the fractional computations used by the rate converter. + * Normally you should never have to modify this value. + * This stuff is defined in common/frac.h, but we redefine it here as the + * ARM routine we call doesn't respect those definitions. + */ +#define FRAC_BITS 16 +#define FRAC_ONE (1< +class SimpleRateConverter : public RateConverter { +protected: + SimpleRateDetails sr; +public: + SimpleRateConverter(st_rate_t inrate, st_rate_t outrate); + int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r); + int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { + return (ST_SUCCESS); + } +}; + + +/* + * Prepare processing. + */ +template +SimpleRateConverter::SimpleRateConverter(st_rate_t inrate, st_rate_t outrate) { + if (inrate == outrate) { + error("Input and Output rates must be different to use rate effect"); + } + + if ((inrate % outrate) != 0) { + error("Input rate must be a multiple of Output rate to use rate effect"); + } + + if (inrate >= 65536 || outrate >= 65536) { + error("rate effect can only handle rates < 65536"); + } + + sr.opos = 1; + + /* increment */ + sr.opos_inc = inrate / outrate; + + sr.inLen = 0; +} + +extern "C" { +#ifndef IPHONE +#define ARM_SimpleRate_M _ARM_SimpleRate_M +#define ARM_SimpleRate_S _ARM_SimpleRate_S +#define ARM_SimpleRate_R _ARM_SimpleRate_R +#endif +} + +extern "C" st_sample_t *ARM_SimpleRate_M( + AudioStream &input, + int (*fn)(Audio::AudioStream&,int16*,int), + SimpleRateDetails *sr, + st_sample_t *obuf, + st_size_t osamp, + st_volume_t vol_l, + st_volume_t vol_r); + +extern "C" st_sample_t *ARM_SimpleRate_S( + AudioStream &input, + int (*fn)(Audio::AudioStream&,int16*,int), + SimpleRateDetails *sr, + st_sample_t *obuf, + st_size_t osamp, + st_volume_t vol_l, + st_volume_t vol_r); + +extern "C" st_sample_t *ARM_SimpleRate_R( + AudioStream &input, + int (*fn)(Audio::AudioStream&,int16*,int), + SimpleRateDetails *sr, + st_sample_t *obuf, + st_size_t osamp, + st_volume_t vol_l, + st_volume_t vol_r); + +extern "C" int SimpleRate_readFudge(Audio::AudioStream &input, + int16 *a, int b) +{ +#ifdef DEBUG_RATECONV + debug("Reading ptr=%x n%d", a, b); +#endif + return input.readBuffer(a, b); +} + +template +int SimpleRateConverter::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { + +#ifdef DEBUG_RATECONV + debug("Simple st=%d rev=%d", stereo, reverseStereo); +#endif + st_sample_t *ostart = obuf; + + if (!stereo) { + obuf = ARM_SimpleRate_M(input, + &SimpleRate_readFudge, + &sr, + obuf, osamp, vol_l, vol_r); + } else if (reverseStereo) { + obuf = ARM_SimpleRate_R(input, + &SimpleRate_readFudge, + &sr, + obuf, osamp, vol_l, vol_r); + } else { + obuf = ARM_SimpleRate_S(input, + &SimpleRate_readFudge, + &sr, + obuf, osamp, vol_l, vol_r); + } + return (obuf-ostart)/2; +} + +/** + * Audio rate converter based on simple linear Interpolation. + * + * The use of fractional increment allows us to use no buffer. It + * avoid the problems at the end of the buffer we had with the old + * method which stored a possibly big buffer of size + * lcm(in_rate,out_rate). + * + * Limited to sampling frequency <= 65535 Hz. + */ + +typedef struct { + const st_sample_t *inPtr; + int inLen; + + /** position of how far output is ahead of input */ + /** Holds what would have been opos-ipos<<16 + opos_frac */ + long opos; + + /** integer position increment in the output stream */ + long opos_inc; + + /** current sample(s) in the input stream (left/right channel) */ + st_sample_t icur[2]; + /** last sample(s) in the input stream (left/right channel) */ + /** Note, these are deliberately ints, not st_sample_t's */ + int32 ilast[2]; + + st_sample_t inBuf[INTERMEDIATE_BUFFER_SIZE]; +} LinearRateDetails; + +extern "C" { +#ifndef IPHONE +#define ARM_LinearRate_M _ARM_LinearRate_M +#define ARM_LinearRate_S _ARM_LinearRate_S +#define ARM_LinearRate_R _ARM_LinearRate_R +#endif +} + +extern "C" st_sample_t *ARM_LinearRate_M( + AudioStream &input, + int (*fn)(Audio::AudioStream&,int16*,int), + LinearRateDetails *lr, + st_sample_t *obuf, + st_size_t osamp, + st_volume_t vol_l, + st_volume_t vol_r); + +extern "C" st_sample_t *ARM_LinearRate_S( + AudioStream &input, + int (*fn)(Audio::AudioStream&,int16*,int), + LinearRateDetails *lr, + st_sample_t *obuf, + st_size_t osamp, + st_volume_t vol_l, + st_volume_t vol_r); + +extern "C" st_sample_t *ARM_LinearRate_R( + AudioStream &input, + int (*fn)(Audio::AudioStream&,int16*,int), + LinearRateDetails *lr, + st_sample_t *obuf, + st_size_t osamp, + st_volume_t vol_l, + st_volume_t vol_r); + +template +class LinearRateConverter : public RateConverter { +protected: + LinearRateDetails lr; + +public: + LinearRateConverter(st_rate_t inrate, st_rate_t outrate); + int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r); + int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { + return (ST_SUCCESS); + } +}; + + +/* + * Prepare processing. + */ +template +LinearRateConverter::LinearRateConverter(st_rate_t inrate, st_rate_t outrate) { + unsigned long incr; + + if (inrate == outrate) { + error("Input and Output rates must be different to use rate effect"); + } + + if (inrate >= 65536 || outrate >= 65536) { + error("rate effect can only handle rates < 65536"); + } + + lr.opos = FRAC_ONE; + + /* increment */ + incr = (inrate << FRAC_BITS) / outrate; + + lr.opos_inc = incr; + + lr.ilast[0] = lr.ilast[1] = 32768; + lr.icur[0] = lr.icur[1] = 0; + + lr.inLen = 0; +} + +/* + * Processed signed long samples from ibuf to obuf. + * Return number of sample pairs processed. + */ +template +int LinearRateConverter::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { + +#ifdef DEBUG_RATECONV + debug("Linear st=%d rev=%d", stereo, reverseStereo); +#endif + st_sample_t *ostart = obuf; + + if (!stereo) { + obuf = ARM_LinearRate_M(input, + &SimpleRate_readFudge, + &lr, + obuf, osamp, vol_l, vol_r); + } else if (reverseStereo) { + obuf = ARM_LinearRate_R(input, + &SimpleRate_readFudge, + &lr, + obuf, osamp, vol_l, vol_r); + } else { + obuf = ARM_LinearRate_S(input, + &SimpleRate_readFudge, + &lr, + obuf, osamp, vol_l, vol_r); + } + return (obuf-ostart)/2; +} + + +#pragma mark - + + +/** + * Simple audio rate converter for the case that the inrate equals the outrate. + */ +extern "C" { +#ifndef IPHONE +#define ARM_CopyRate_M _ARM_CopyRate_M +#define ARM_CopyRate_S _ARM_CopyRate_S +#define ARM_CopyRate_R _ARM_CopyRate_R +#endif +} + +extern "C" st_sample_t *ARM_CopyRate_M( + st_size_t len, + st_sample_t *obuf, + st_volume_t vol_l, + st_volume_t vol_r, + st_sample_t *_buffer); + +extern "C" st_sample_t *ARM_CopyRate_S( + st_size_t len, + st_sample_t *obuf, + st_volume_t vol_l, + st_volume_t vol_r, + st_sample_t *_buffer); + +extern "C" st_sample_t *ARM_CopyRate_R( + st_size_t len, + st_sample_t *obuf, + st_volume_t vol_l, + st_volume_t vol_r, + st_sample_t *_buffer); + + +template +class CopyRateConverter : public RateConverter { + st_sample_t *_buffer; + st_size_t _bufferSize; +public: + CopyRateConverter() : _buffer(0), _bufferSize(0) {} + ~CopyRateConverter() { + free(_buffer); + } + + virtual int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { + assert(input.isStereo() == stereo); + +#ifdef DEBUG_RATECONV + debug("Copy st=%d rev=%d", stereo, reverseStereo); +#endif + st_size_t len; + st_sample_t *ostart = obuf; + + if (stereo) + osamp *= 2; + + // Reallocate temp buffer, if necessary + if (osamp > _bufferSize) { + free(_buffer); + _buffer = (st_sample_t *)malloc(osamp * 2); + _bufferSize = osamp; + } + + // Read up to 'osamp' samples into our temporary buffer + len = input.readBuffer(_buffer, osamp); + if (len <= 0) + return 0; + + // Mix the data into the output buffer + if (stereo && reverseStereo) + obuf = ARM_CopyRate_R(len, obuf, vol_l, vol_r, _buffer); + else if (stereo) + obuf = ARM_CopyRate_S(len, obuf, vol_l, vol_r, _buffer); + else + obuf = ARM_CopyRate_M(len, obuf, vol_l, vol_r, _buffer); + + return (obuf-ostart)/2; + } + virtual int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { + return (ST_SUCCESS); + } +}; + + +#pragma mark - + + +/** + * Create and return a RateConverter object for the specified input and output rates. + */ +RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate, bool stereo, bool reverseStereo) { + if (inrate != outrate) { + if ((inrate % outrate) == 0) { + if (stereo) { + if (reverseStereo) + return new SimpleRateConverter(inrate, outrate); + else + return new SimpleRateConverter(inrate, outrate); + } else + return new SimpleRateConverter(inrate, outrate); + } else { + if (stereo) { + if (reverseStereo) + return new LinearRateConverter(inrate, outrate); + else + return new LinearRateConverter(inrate, outrate); + } else + return new LinearRateConverter(inrate, outrate); + } + } else { + if (stereo) { + if (reverseStereo) + return new CopyRateConverter(); + else + return new CopyRateConverter(); + } else + return new CopyRateConverter(); + } +} + +} // End of namespace Audio diff --git a/audio/rate_arm_asm.s b/audio/rate_arm_asm.s new file mode 100644 index 0000000000..9431ae0649 --- /dev/null +++ b/audio/rate_arm_asm.s @@ -0,0 +1,687 @@ +@ ScummVM - Graphic Adventure Engine +@ +@ ScummVM is the legal property of its developers, whose names +@ are too numerous to list here. Please refer to the COPYRIGHT +@ file distributed with this source distribution. +@ +@ This program is free software@ you can redistribute it and/or +@ modify it under the terms of the GNU General Public License +@ as published by the Free Software Foundation@ either version 2 +@ of the License, or (at your option) any later version. +@ +@ This program is distributed in the hope that it will be useful, +@ but WITHOUT ANY WARRANTY@ without even the implied warranty of +@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +@ GNU General Public License for more details. +@ +@ You should have received a copy of the GNU General Public License +@ along with this program@ if not, write to the Free Software +@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +@ +@ $URL$ +@ $Id$ +@ +@ @author Robin Watts (robin@wss.co.uk) +@ +@ This file, together with rate_arm.cpp, provides an ARM optimised version +@ of rate.cpp. The algorithm is essentially the same as that within rate.cpp +@ so to understand this file you should understand rate.cpp first. + + .text + + .global _ARM_CopyRate_M + .global _ARM_CopyRate_S + .global _ARM_CopyRate_R + .global _ARM_SimpleRate_M + .global _ARM_SimpleRate_S + .global _ARM_SimpleRate_R + .global _ARM_LinearRate_M + .global _ARM_LinearRate_S + .global _ARM_LinearRate_R + +_ARM_CopyRate_M: + @ r0 = len + @ r1 = obuf + @ r2 = vol_l + @ r3 = vol_r + @ <> = ptr + LDR r12,[r13] + STMFD r13!,{r4-r7,r14} + + MOV r14,#0 @ r14= 0 + ORR r2, r2, r2, LSL #8 @ r2 = vol_l as 16 bits + ORR r3, r3, r3, LSL #8 @ r3 = vol_r as 16 bits +CopyRate_M_loop: + LDRSH r5, [r12], #2 @ r5 = tmp0 = tmp1 = *ptr++ + LDRSH r6, [r1] @ r6 = obuf[0] + LDRSH r7, [r1, #2] @ r7 = obuf[1] + MUL r4, r2, r5 @ r4 = tmp0*vol_l + MUL r5, r3, r5 @ r5 = tmp1*vol_r + + ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l + RSCVS r6, r14,#0x80000000 @ Clamp r6 + ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r + RSCVS r7, r14,#0x80000000 @ Clamp r7 + + MOV r6, r6, LSR #16 @ Shift back to halfword + MOV r7, r7, LSR #16 @ Shift back to halfword + + STRH r6, [r1], #2 @ Store output value + STRH r7, [r1], #2 @ Store output value + + SUBS r0,r0,#1 @ len-- + BGT CopyRate_M_loop @ and loop + + MOV r0, r1 @ return obuf + + LDMFD r13!,{r4-r7,PC} + +_ARM_CopyRate_S: + @ r0 = len + @ r1 = obuf + @ r2 = vol_l + @ r3 = vol_r + @ <> = ptr + LDR r12,[r13] + STMFD r13!,{r4-r7,r14} + + MOV r14,#0 @ r14= 0 + ORR r2, r2, r2, LSL #8 @ r2 = vol_l as 16 bits + ORR r3, r3, r3, LSL #8 @ r3 = vol_r as 16 bits +CopyRate_S_loop: + LDRSH r4, [r12],#2 @ r4 = tmp0 = *ptr++ + LDRSH r5, [r12],#2 @ r5 = tmp1 = *ptr++ + LDRSH r6, [r1] @ r6 = obuf[0] + LDRSH r7, [r1,#2] @ r7 = obuf[1] + MUL r4, r2, r4 @ r5 = tmp0*vol_l + MUL r5, r3, r5 @ r6 = tmp1*vol_r + + ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l + RSCVS r6, r14,#0x80000000 @ Clamp r6 + ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r + RSCVS r7, r14,#0x80000000 @ Clamp r7 + + MOV r6, r6, LSR #16 @ Shift back to halfword + MOV r7, r7, LSR #16 @ Shift back to halfword + + STRH r6, [r1],#2 @ Store output value + STRH r7, [r1],#2 @ Store output value + + SUBS r0,r0,#2 @ len -= 2 + BGT CopyRate_S_loop @ and loop + + MOV r0, r1 @ return obuf + + LDMFD r13!,{r4-r7,PC} + +_ARM_CopyRate_R: + @ r0 = len + @ r1 = obuf + @ r2 = vol_l + @ r3 = vol_r + @ <> = ptr + LDR r12,[r13] + STMFD r13!,{r4-r7,r14} + + MOV r14,#0 @ r14= 0 + ORR r2, r2, r2, LSL #8 @ r2 = vol_l as 16 bits + ORR r3, r3, r3, LSL #8 @ r3 = vol_r as 16 bits +CopyRate_R_loop: + LDRSH r5, [r12],#2 @ r5 = tmp1 = *ptr++ + LDRSH r4, [r12],#2 @ r4 = tmp0 = *ptr++ + LDRSH r6, [r1] @ r6 = obuf[0] + LDRSH r7, [r1,#2] @ r7 = obuf[1] + MUL r4, r2, r4 @ r4 = tmp0*vol_l + MUL r5, r3, r5 @ r5 = tmp1*vol_r + + ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l + RSCVS r6, r14,#0x80000000 @ Clamp r6 + ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r + RSCVS r7, r14,#0x80000000 @ Clamp r7 + + MOV r6, r6, LSR #16 @ Shift back to halfword + MOV r7, r7, LSR #16 @ Shift back to halfword + + STRH r6, [r1],#2 @ Store output value + STRH r7, [r1],#2 @ Store output value + + SUBS r0,r0,#2 @ len -= 2 + BGT CopyRate_R_loop @ and loop + + MOV r0, r1 @ return obuf + + LDMFD r13!,{r4-r7,PC} + +_ARM_SimpleRate_M: + @ r0 = AudioStream &input + @ r1 = input.readBuffer + @ r2 = input->sr + @ r3 = obuf + @ <> = osamp + @ <> = vol_l + @ <> = vol_r + MOV r12,r13 + STMFD r13!,{r0-r2,r4-r8,r10-r11,r14} + LDMFD r12,{r11,r12,r14} @ r11= osamp + @ r12= vol_l + @ r14= vol_r + LDMIA r2,{r0,r1,r2,r8} @ r0 = inPtr + @ r1 = inLen + @ r2 = opos + @ r8 = opos_inc + CMP r11,#0 @ if (osamp <= 0) + BLE SimpleRate_M_end @ bale + MOV r10,#0 + ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits + ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits +SimpleRate_M_loop: + SUBS r1, r1, #1 @ r1 = inLen -= 1 + BLT SimpleRate_M_read + SUBS r2, r2, #1 @ r2 = opos-- + ADDGE r0, r0, #2 @ if (r2 >= 0) { sr.inPtr++ + BGE SimpleRate_M_loop @ and loop } +SimpleRate_M_read_return: + LDRSH r5, [r0],#2 @ r5 = tmp1 = *inPtr++ + LDRSH r6, [r3] @ r6 = obuf[0] + LDRSH r7, [r3,#2] @ r7 = obuf[1] + ADD r2, r2, r8 @ r2 = opos += opos_inc + MUL r4, r12,r5 @ r4 = tmp0*vol_l + MUL r5, r14,r5 @ r5 = tmp1*vol_r + + ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l + RSCVS r6, r10,#0x80000000 @ Clamp r6 + ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r + RSCVS r7, r10,#0x80000000 @ Clamp r7 + + MOV r6, r6, LSR #16 @ Shift back to halfword + MOV r7, r7, LSR #16 @ Shift back to halfword + + STRH r6, [r3],#2 @ Store output value + STRH r7, [r3],#2 @ Store output value + + SUBS r11,r11,#1 @ len-- + BGT SimpleRate_M_loop @ and loop +SimpleRate_M_end: + LDR r14,[r13,#8] @ r14 = sr + ADD r13,r13,#12 @ Skip over r0-r2 on stack + STMIA r14,{r0,r1,r2} @ Store back updated values + + MOV r0, r3 @ return obuf + + LDMFD r13!,{r4-r8,r10-r11,PC} +SimpleRate_M_read: + LDR r0, [r13,#8] @ r0 = sr (8 = 4*2) + ADD r0, r0, #16 @ r0 = inPtr = inBuf + STMFD r13!,{r0,r2-r3,r12,r14} + + MOV r1, r0 @ r1 = inBuf + LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5) + MOV r2, #512 @ r2 = ARRAYSIZE(inBuf) + + @ Calling back into C++ here. WinCE is fairly easy about such things + @ but other OS are more awkward. r9 is preserved for Symbian, and + @ we have 3+8+5 = 16 things on the stack (an even number). + MOV r14,PC + LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6) + SUBS r1, r0, #1 @ r1 = inLen-1 + LDMFD r13!,{r0,r2-r3,r12,r14} + BLT SimpleRate_M_end + SUBS r2, r2, #1 @ r2 = opos-- + ADDGE r0, r0, #2 @ if (r2 >= 0) { sr.inPtr++ + BGE SimpleRate_M_loop @ and loop } + B SimpleRate_M_read_return + + +_ARM_SimpleRate_S: + @ r0 = AudioStream &input + @ r1 = input.readBuffer + @ r2 = input->sr + @ r3 = obuf + @ <> = osamp + @ <> = vol_l + @ <> = vol_r + MOV r12,r13 + STMFD r13!,{r0-r2,r4-r8,r10-r11,r14} + LDMFD r12,{r11,r12,r14} @ r11= osamp + @ r12= vol_l + @ r14= vol_r + LDMIA r2,{r0,r1,r2,r8} @ r0 = inPtr + @ r1 = inLen + @ r2 = opos + @ r8 = opos_inc + CMP r11,#0 @ if (osamp <= 0) + BLE SimpleRate_S_end @ bale + MOV r10,#0 + ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits + ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits +SimpleRate_S_loop: + SUBS r1, r1, #2 @ r1 = inLen -= 2 + BLT SimpleRate_S_read + SUBS r2, r2, #1 @ r2 = opos-- + ADDGE r0, r0, #4 @ if (r2 >= 0) { sr.inPtr += 2 + BGE SimpleRate_S_loop @ and loop } +SimpleRate_S_read_return: + LDRSH r4, [r0],#2 @ r4 = tmp0 = *inPtr++ + LDRSH r5, [r0],#2 @ r5 = tmp1 = *inPtr++ + LDRSH r6, [r3] @ r6 = obuf[0] + LDRSH r7, [r3,#2] @ r7 = obuf[1] + ADD r2, r2, r8 @ r2 = opos += opos_inc + MUL r4, r12,r4 @ r5 = tmp0*vol_l + MUL r5, r14,r5 @ r6 = tmp1*vol_r + + ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l + RSCVS r6, r10,#0x80000000 @ Clamp r6 + ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r + RSCVS r7, r10,#0x80000000 @ Clamp r7 + + MOV r6, r6, LSR #16 @ Shift back to halfword + MOV r7, r7, LSR #16 @ Shift back to halfword + + STRH r6, [r3],#2 @ Store output value + STRH r7, [r3],#2 @ Store output value + + SUBS r11,r11,#1 @ osamp-- + BGT SimpleRate_S_loop @ and loop +SimpleRate_S_end: + LDR r14,[r13,#8] @ r14 = sr + ADD r13,r13,#12 @ skip over r0-r2 on stack + STMIA r14,{r0,r1,r2} @ store back updated values + MOV r0, r3 @ return obuf + LDMFD r13!,{r4-r8,r10-r11,PC} +SimpleRate_S_read: + LDR r0, [r13,#8] @ r0 = sr (8 = 4*2) + ADD r0, r0, #16 @ r0 = inPtr = inBuf + STMFD r13!,{r0,r2-r3,r12,r14} + MOV r1, r0 @ r1 = inBuf + LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5) + MOV r2, #512 @ r2 = ARRAYSIZE(inBuf) + + @ Calling back into C++ here. WinCE is fairly easy about such things + @ but other OS are more awkward. r9 is preserved for Symbian, and + @ we have 3+8+5 = 16 things on the stack (an even number). + MOV r14,PC + LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6) + SUBS r1, r0, #2 @ r1 = inLen-2 + LDMFD r13!,{r0,r2-r3,r12,r14} + BLT SimpleRate_S_end + SUBS r2, r2, #1 @ r2 = opos-- + ADDGE r0, r0, #4 @ if (r2 >= 0) { sr.inPtr += 2 + BGE SimpleRate_S_loop @ and loop } + B SimpleRate_S_read_return + + + +_ARM_SimpleRate_R: + @ r0 = AudioStream &input + @ r1 = input.readBuffer + @ r2 = input->sr + @ r3 = obuf + @ <> = osamp + @ <> = vol_l + @ <> = vol_r + MOV r12,r13 + STMFD r13!,{r0-r2,r4-r8,r10-r11,r14} + LDMFD r12,{r11,r12,r14} @ r11= osamp + @ r12= vol_l + @ r14= vol_r + LDMIA r2,{r0,r1,r2,r8} @ r0 = inPtr + @ r1 = inLen + @ r2 = opos + @ r8 = opos_inc + CMP r11,#0 @ if (osamp <= 0) + BLE SimpleRate_R_end @ bale + MOV r10,#0 + ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits + ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits +SimpleRate_R_loop: + SUBS r1, r1, #2 @ r1 = inLen -= 2 + BLT SimpleRate_R_read + SUBS r2, r2, #1 @ r2 = opos-- + ADDGE r0, r0, #4 @ if (r2 >= 0) { sr.inPtr += 2 + BGE SimpleRate_R_loop @ and loop } +SimpleRate_R_read_return: + LDRSH r5, [r0],#2 @ r5 = tmp0 = *inPtr++ + LDRSH r4, [r0],#2 @ r4 = tmp1 = *inPtr++ + LDRSH r6, [r3] @ r6 = obuf[0] + LDRSH r7, [r3,#2] @ r7 = obuf[1] + ADD r2, r2, r8 @ r2 = opos += opos_inc + MUL r4, r12,r4 @ r5 = tmp0*vol_l + MUL r5, r14,r5 @ r6 = tmp1*vol_r + + ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l + RSCVS r6, r10,#0x80000000 @ Clamp r6 + ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r + RSCVS r7, r10,#0x80000000 @ Clamp r7 + + MOV r6, r6, LSR #16 @ Shift back to halfword + MOV r7, r7, LSR #16 @ Shift back to halfword + + STRH r6, [r3],#2 @ Store output value + STRH r7, [r3],#2 @ Store output value + + SUBS r11,r11,#1 @ osamp-- + BGT SimpleRate_R_loop @ and loop +SimpleRate_R_end: + LDR r14,[r13,#8] @ r14 = sr + ADD r13,r13,#12 @ Skip over r0-r2 on stack + STMIA r14,{r0,r1,r2} @ Store back updated values + MOV r0, r3 @ return obuf + LDMFD r13!,{r4-r8,r10-r11,PC} +SimpleRate_R_read: + LDR r0, [r13,#8] @ r0 = sr (8 = 4*2) + ADD r0, r0, #16 @ r0 = inPtr = inBuf + STMFD r13!,{r0,r2-r3,r12,r14} + + MOV r1, r0 @ r1 = inBuf + LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5) + MOV r2, #512 @ r2 = ARRAYSIZE(inBuf) + + @ Calling back into C++ here. WinCE is fairly easy about such things + @ but other OS are more awkward. r9 is preserved for Symbian, and + @ we have 3+8+5 = 16 things on the stack (an even number). + MOV r14,PC + LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6) + SUBS r1, r0, #2 @ r1 = inLen-2 + LDMFD r13!,{r0,r2-r3,r12,r14} + BLT SimpleRate_R_end + SUBS r2, r2, #1 @ r2 = opos-- + ADDGE r0, r0, #4 @ if (r2 >= 0) { sr.inPtr += 2 + BGE SimpleRate_R_loop @ and loop } + B SimpleRate_R_read_return + + +_ARM_LinearRate_M: + @ r0 = AudioStream &input + @ r1 = input.readBuffer + @ r2 = input->sr + @ r3 = obuf + @ <> = osamp + @ <> = vol_l + @ <> = vol_r + MOV r12,r13 + STMFD r13!,{r0-r1,r4-r11,r14} + LDMFD r12,{r11,r12,r14} @ r11= osamp + @ r12= vol_l + @ r14= vol_r + LDMIA r2,{r0,r1,r8} @ r0 = inPtr + @ r1 = inLen + @ r8 = opos + MOV r10,#0 + CMP r11,#0 @ if (osamp <= 0) + BLE LinearRate_M_end @ bale + ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits + ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits + CMP r1,#0 + BGT LinearRate_M_part2 + + @ part1 - read input samples +LinearRate_M_loop: + SUBS r1, r1, #1 @ r1 = inLen -= 1 + BLT LinearRate_M_read +LinearRate_M_read_return: + LDRH r4, [r2, #16] @ r4 = icur[0] + LDRSH r5, [r0],#2 @ r5 = tmp1 = *inPtr++ + SUBS r8, r8, #65536 @ r8 = opos-- + STRH r4, [r2,#22] @ ilast[0] = icur[0] + STRH r5, [r2,#16] @ icur[0] = tmp1 + BGE LinearRate_M_loop + + @ part2 - form output samples +LinearRate_M_part2: + @ We are guaranteed that opos < 0 here + LDR r6, [r2,#20] @ r6 = ilast[0]<<16 + 32768 + LDRSH r5, [r2,#16] @ r5 = icur[0] + MOV r4, r8, LSL #16 + MOV r4, r4, LSR #16 + SUB r5, r5, r6, ASR #16 @ r5 = icur[0] - ilast[0] + MLA r6, r4, r5, r6 @ r6 = (icur[0]-ilast[0])*opos_frac+ilast[0] + + LDRSH r4, [r3] @ r4 = obuf[0] + LDRSH r5, [r3,#2] @ r5 = obuf[1] + MOV r6, r6, ASR #16 @ r6 = tmp0 = tmp1 >>= 16 + MUL r7, r12,r6 @ r7 = tmp0*vol_l + MUL r6, r14,r6 @ r6 = tmp1*vol_r + + ADDS r7, r7, r4, LSL #16 @ r7 = obuf[0]<<16 + tmp0*vol_l + RSCVS r7, r10, #0x80000000 @ Clamp r7 + ADDS r6, r6, r5, LSL #16 @ r6 = obuf[1]<<16 + tmp1*vol_r + RSCVS r6, r10, #0x80000000 @ Clamp r6 + + MOV r7, r7, LSR #16 @ Shift back to halfword + MOV r6, r6, LSR #16 @ Shift back to halfword + + LDR r5, [r2,#12] @ r5 = opos_inc + STRH r7, [r3],#2 @ Store output value + STRH r6, [r3],#2 @ Store output value + SUBS r11, r11,#1 @ osamp-- + BLE LinearRate_M_end @ end if needed + + ADDS r8, r8, r5 @ r8 = opos += opos_inc + BLT LinearRate_M_part2 + B LinearRate_M_loop +LinearRate_M_end: + ADD r13,r13,#8 + STMIA r2,{r0,r1,r8} + MOV r0, r3 @ return obuf + LDMFD r13!,{r4-r11,PC} +LinearRate_M_read: + ADD r0, r2, #28 @ r0 = inPtr = inBuf + STMFD r13!,{r0,r2-r3,r12,r14} + + MOV r1, r0 @ r1 = inBuf + LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5) + MOV r2, #512 @ r2 = ARRAYSIZE(inBuf) + + @ Calling back into C++ here. WinCE is fairly easy about such things + @ but other OS are more awkward. r9 is preserved for Symbian, and + @ we have 2+9+5 = 16 things on the stack (an even number). + MOV r14,PC + LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6) + SUBS r1, r0, #1 @ r1 = inLen-1 + LDMFD r13!,{r0,r2-r3,r12,r14} + BLT LinearRate_M_end + B LinearRate_M_read_return + +_ARM_LinearRate_S: + @ r0 = AudioStream &input + @ r1 = input.readBuffer + @ r2 = input->sr + @ r3 = obuf + @ <> = osamp + @ <> = vol_l + @ <> = vol_r + MOV r12,r13 + STMFD r13!,{r0-r1,r4-r11,r14} + LDMFD r12,{r11,r12,r14} @ r11= osamp + @ r12= vol_l + @ r14= vol_r + LDMIA r2,{r0,r1,r8} @ r0 = inPtr + @ r1 = inLen + @ r8 = opos + CMP r11,#0 @ if (osamp <= 0) + BLE LinearRate_S_end @ bale + ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits + ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits + CMP r1,#0 + BGT LinearRate_S_part2 + + @ part1 - read input samples +LinearRate_S_loop: + SUBS r1, r1, #2 @ r1 = inLen -= 2 + BLT LinearRate_S_read +LinearRate_S_read_return: + LDR r10,[r2, #16] @ r10= icur[0,1] + LDRSH r5, [r0],#2 @ r5 = tmp0 = *inPtr++ + LDRSH r6, [r0],#2 @ r5 = tmp1 = *inPtr++ + SUBS r8, r8, #65536 @ r8 = opos-- + STRH r10,[r2,#22] @ ilast[0] = icur[0] + MOV r10,r10,LSR #16 + STRH r10,[r2,#26] @ ilast[1] = icur[1] + STRH r5, [r2,#16] @ icur[0] = tmp0 + STRH r6, [r2,#18] @ icur[1] = tmp1 + BGE LinearRate_S_loop + + @ part2 - form output samples +LinearRate_S_part2: + @ We are guaranteed that opos < 0 here + LDR r6, [r2,#20] @ r6 = ilast[0]<<16 + 32768 + LDRSH r5, [r2,#16] @ r5 = icur[0] + MOV r4, r8, LSL #16 + MOV r4, r4, LSR #16 + SUB r5, r5, r6, ASR #16 @ r5 = icur[0] - ilast[0] + MLA r6, r4, r5, r6 @ r6 = (icur[0]-ilast[0])*opos_frac+ilast[0] + + LDR r7, [r2,#24] @ r7 = ilast[1]<<16 + 32768 + LDRSH r5, [r2,#18] @ r5 = icur[1] + LDRSH r10,[r3] @ r10= obuf[0] + MOV r6, r6, ASR #16 @ r6 = tmp1 >>= 16 + SUB r5, r5, r7, ASR #16 @ r5 = icur[1] - ilast[1] + MLA r7, r4, r5, r7 @ r7 = (icur[1]-ilast[1])*opos_frac+ilast[1] + + LDRSH r5, [r3,#2] @ r5 = obuf[1] + MOV r7, r7, ASR #16 @ r7 = tmp0 >>= 16 + MUL r7, r12,r7 @ r7 = tmp0*vol_l + MUL r6, r14,r6 @ r6 = tmp1*vol_r + + ADDS r7, r7, r10, LSL #16 @ r7 = obuf[0]<<16 + tmp0*vol_l + MOV r4, #0 + RSCVS r7, r4, #0x80000000 @ Clamp r7 + ADDS r6, r6, r5, LSL #16 @ r6 = obuf[1]<<16 + tmp1*vol_r + RSCVS r6, r4, #0x80000000 @ Clamp r6 + + MOV r7, r7, LSR #16 @ Shift back to halfword + MOV r6, r6, LSR #16 @ Shift back to halfword + + LDR r5, [r2,#12] @ r5 = opos_inc + STRH r7, [r3],#2 @ Store output value + STRH r6, [r3],#2 @ Store output value + SUBS r11, r11,#1 @ osamp-- + BLE LinearRate_S_end @ and loop + + ADDS r8, r8, r5 @ r8 = opos += opos_inc + BLT LinearRate_S_part2 + B LinearRate_S_loop +LinearRate_S_end: + ADD r13,r13,#8 + STMIA r2,{r0,r1,r8} + MOV r0, r3 @ return obuf + LDMFD r13!,{r4-r11,PC} +LinearRate_S_read: + ADD r0, r2, #28 @ r0 = inPtr = inBuf + STMFD r13!,{r0,r2-r3,r12,r14} + + MOV r1, r0 @ r1 = inBuf + LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5) + MOV r2, #512 @ r2 = ARRAYSIZE(inBuf) + + @ Calling back into C++ here. WinCE is fairly easy about such things + @ but other OS are more awkward. r9 is preserved for Symbian, and + @ we have 2+9+5 = 16 things on the stack (an even number). + MOV r14,PC + LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6) + SUBS r1, r0, #2 @ r1 = inLen-2 + LDMFD r13!,{r0,r2-r3,r12,r14} + BLT LinearRate_S_end + B LinearRate_S_read_return + +_ARM_LinearRate_R: + @ r0 = AudioStream &input + @ r1 = input.readBuffer + @ r2 = input->sr + @ r3 = obuf + @ <> = osamp + @ <> = vol_l + @ <> = vol_r + MOV r12,r13 + STMFD r13!,{r0-r1,r4-r11,r14} + LDMFD r12,{r11,r12,r14} @ r11= osamp + @ r12= vol_l + @ r14= vol_r + LDMIA r2,{r0,r1,r8} @ r0 = inPtr + @ r1 = inLen + @ r8 = opos + CMP r11,#0 @ if (osamp <= 0) + BLE LinearRate_R_end @ bale + ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits + ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits + CMP r1,#0 + BGT LinearRate_R_part2 + + @ part1 - read input samples +LinearRate_R_loop: + SUBS r1, r1, #2 @ r1 = inLen -= 2 + BLT LinearRate_R_read +LinearRate_R_read_return: + LDR r10,[r2, #16] @ r10= icur[0,1] + LDRSH r5, [r0],#2 @ r5 = tmp0 = *inPtr++ + LDRSH r6, [r0],#2 @ r5 = tmp1 = *inPtr++ + SUBS r8, r8, #65536 @ r8 = opos-- + STRH r10,[r2,#22] @ ilast[0] = icur[0] + MOV r10,r10,LSR #16 + STRH r10,[r2,#22] @ ilast[1] = icur[1] + STRH r5, [r2,#16] @ icur[0] = tmp0 + STRH r6, [r2,#18] @ icur[1] = tmp1 + BGE LinearRate_R_loop + + @ part2 - form output samples +LinearRate_R_part2: + @ We are guaranteed that opos < 0 here + LDR r6, [r2,#20] @ r6 = ilast[0] + LDRSH r5, [r2,#16] @ r5 = icur[0] + MOV r4, r8, LSL #16 + MOV r4, r4, LSR #16 + SUB r5, r5, r6, ASR #16 @ r5 = icur[0] - ilast[0] + MLA r6, r4, r5, r6 @ r6 = (icur[0]-ilast[0])*opos_frac+ilast[0] + + LDR r7, [r2,#24] @ r7 = ilast[1] + LDRSH r5, [r2,#18] @ r5 = icur[1] + LDR r10,[r3] @ r10= obuf[0] + MOV r6, r6, ASR #16 @ r6 = tmp1 >>= 16 + SUB r5, r5, r7, ASR #16 @ r5 = icur[1] - ilast[1] + MLA r7, r4, r5, r7 @ r7 = (icur[1]-ilast[1])*opos_frac+ilast[1] + + LDRSH r5, [r3,#2] @ r5 = obuf[1] + MOV r7, r7, ASR #16 @ r7 = tmp0 >>= 16 + MUL r7, r12,r7 @ r7 = tmp0*vol_l + MUL r6, r14,r6 @ r6 = tmp1*vol_r + + ADDS r7, r7, r10, LSL #16 @ r7 = obuf[0]<<16 + tmp0*vol_l + MOV r4, #0 + RSCVS r7, r4, #0x80000000 @ Clamp r7 + ADDS r6, r6, r5, LSL #16 @ r6 = obuf[1]<<16 + tmp1*vol_r + RSCVS r6, r4, #0x80000000 @ Clamp r6 + + MOV r7, r7, LSR #16 @ Shift back to halfword + MOV r6, r6, LSR #16 @ Shift back to halfword + + LDR r5, [r2,#12] @ r5 = opos_inc + STRH r6, [r3],#2 @ Store output value + STRH r7, [r3],#2 @ Store output value + SUBS r11, r11,#1 @ osamp-- + BLE LinearRate_R_end @ and loop + + ADDS r8, r8, r5 @ r8 = opos += opos_inc + BLT LinearRate_R_part2 + B LinearRate_R_loop +LinearRate_R_end: + ADD r13,r13,#8 + STMIA r2,{r0,r1,r8} + MOV r0, r3 @ return obuf + LDMFD r13!,{r4-r11,PC} +LinearRate_R_read: + ADD r0, r2, #28 @ r0 = inPtr = inBuf + STMFD r13!,{r0,r2-r3,r12,r14} + + MOV r1, r0 @ r1 = inBuf + LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5) + MOV r2, #512 @ r2 = ARRAYSIZE(inBuf) + + @ Calling back into C++ here. WinCE is fairly easy about such things + @ but other OS are more awkward. r9 is preserved for Symbian, and + @ we have 2+9+5 = 16 things on the stack (an even number). + MOV r14,PC + LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6) + SUBS r1, r0, #2 @ r1 = inLen-2 + LDMFD r13!,{r0,r2-r3,r12,r14} + BLT LinearRate_R_end + B LinearRate_R_read_return diff --git a/audio/softsynth/adlib.cpp b/audio/softsynth/adlib.cpp new file mode 100644 index 0000000000..4a9ce54c75 --- /dev/null +++ b/audio/softsynth/adlib.cpp @@ -0,0 +1,1617 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "audio/softsynth/emumidi.h" +#include "common/debug.h" +#include "common/util.h" +#include "audio/fmopl.h" +#include "audio/musicplugin.h" +#include "common/translation.h" + +#ifdef DEBUG_ADLIB +static int tick; +#endif + +class MidiDriver_ADLIB; +struct AdLibVoice; + +// We use packing for the following two structs, because the code +// does simply copy them over from byte streams, without any +// serialization. Check AdLibPart::sysEx_customInstrument for an +// example of this. +// +// It might be very well possible, that none of the compilers we support +// add any padding bytes at all, since the structs contain only variables +// of the type 'byte'. But better safe than sorry. +#include "common/pack-start.h" +struct InstrumentExtra { + byte a, b, c, d, e, f, g, h; +} PACKED_STRUCT; + +struct AdLibInstrument { + byte mod_characteristic; + byte mod_scalingOutputLevel; + byte mod_attackDecay; + byte mod_sustainRelease; + byte mod_waveformSelect; + byte car_characteristic; + byte car_scalingOutputLevel; + byte car_attackDecay; + byte car_sustainRelease; + byte car_waveformSelect; + byte feedback; + byte flags_a; + InstrumentExtra extra_a; + byte flags_b; + InstrumentExtra extra_b; + byte duration; + + AdLibInstrument() { memset(this, 0, sizeof(AdLibInstrument)); } +} PACKED_STRUCT; +#include "common/pack-end.h" + +class AdLibPart : public MidiChannel { + friend class MidiDriver_ADLIB; + +protected: +// AdLibPart *_prev, *_next; + AdLibVoice *_voice; + int16 _pitchbend; + byte _pitchbend_factor; + int8 _transpose_eff; + byte _vol_eff; + int8 _detune_eff; + byte _modwheel; + bool _pedal; + byte _program; + byte _pri_eff; + AdLibInstrument _part_instr; + +protected: + MidiDriver_ADLIB *_owner; + bool _allocated; + byte _channel; + + void init(MidiDriver_ADLIB *owner, byte channel); + void allocate() { _allocated = true; } + +public: + AdLibPart() { + _voice = 0; + _pitchbend = 0; + _pitchbend_factor = 2; + _transpose_eff = 0; + _vol_eff = 0; + _detune_eff = 0; + _modwheel = 0; + _pedal = 0; + _program = 0; + _pri_eff = 0; + + _owner = 0; + _allocated = false; + _channel = 0; + } + + MidiDriver *device(); + byte getNumber() { return _channel; } + void release() { _allocated = false; } + + void send (uint32 b); + + // Regular messages + void noteOff(byte note); + void noteOn(byte note, byte velocity); + void programChange(byte program); + void pitchBend(int16 bend); + + // Control Change messages + void controlChange(byte control, byte value); + void modulationWheel(byte value); + void volume(byte value); + void panPosition(byte value) { return; } // Not supported + void pitchBendFactor(byte value); + void detune(byte value); + void priority(byte value); + void sustain(bool value); + void effectLevel(byte value) { return; } // Not supported + void chorusLevel(byte value) { return; } // Not supported + void allNotesOff(); + + // SysEx messages + void sysEx_customInstrument(uint32 type, const byte *instr); +}; + +// FYI (Jamieson630) +// It is assumed that any invocation to AdLibPercussionChannel +// will be done through the MidiChannel base class as opposed to the +// AdLibPart base class. If this were NOT the case, all the functions +// listed below would need to be virtual in AdLibPart as well as MidiChannel. +class AdLibPercussionChannel : public AdLibPart { + friend class MidiDriver_ADLIB; + +protected: + void init(MidiDriver_ADLIB *owner, byte channel); + +public: + ~AdLibPercussionChannel(); + + void noteOff(byte note); + void noteOn(byte note, byte velocity); + void programChange(byte program) { } + void pitchBend(int16 bend) { } + + // Control Change messages + void modulationWheel(byte value) { } + void pitchBendFactor(byte value) { } + void detune(byte value) { } + void priority(byte value) { } + void sustain(bool value) { } + + // SysEx messages + void sysEx_customInstrument(uint32 type, const byte *instr); + +private: + byte _notes[256]; + AdLibInstrument *_customInstruments[256]; +}; + +struct Struct10 { + byte active; + int16 cur_val; + int16 count; + uint16 max_value; + int16 start_value; + byte loop; + byte table_a[4]; + byte table_b[4]; + int8 unk3; + int8 modwheel; + int8 modwheel_last; + uint16 speed_lo_max; + uint16 num_steps; + int16 speed_hi; + int8 direction; + uint16 speed_lo; + uint16 speed_lo_counter; +}; + +struct Struct11 { + int16 modify_val; + byte param, flag0x40, flag0x10; + Struct10 *s10; +}; + +struct AdLibVoice { + AdLibPart *_part; + AdLibVoice *_next, *_prev; + byte _waitforpedal; + byte _note; + byte _channel; + byte _twochan; + byte _vol_1, _vol_2; + int16 _duration; + + Struct10 _s10a; + Struct11 _s11a; + Struct10 _s10b; + Struct11 _s11b; + + AdLibVoice() { memset(this, 0, sizeof(AdLibVoice)); } +}; + +struct AdLibSetParams { + byte a, b, c, d; +}; + +static const byte channel_mappings[9] = { + 0, 1, 2, 8, + 9, 10, 16, 17, + 18 +}; + +static const byte channel_mappings_2[9] = { + 3, 4, 5, 11, + 12, 13, 19, 20, + 21 +}; + +static const AdLibSetParams adlib_setparam_table[] = { + {0x40, 0, 63, 63}, // level + {0xE0, 2, 0, 0}, // unused + {0x40, 6, 192, 0}, // level key scaling + {0x20, 0, 15, 0}, // modulator frequency multiple + {0x60, 4, 240, 15}, // attack rate + {0x60, 0, 15, 15}, // decay rate + {0x80, 4, 240, 15}, // sustain level + {0x80, 0, 15, 15}, // release rate + {0xE0, 0, 3, 0}, // waveformSelect select + {0x20, 7, 128, 0}, // amp mod + {0x20, 6, 64, 0}, // vib + {0x20, 5, 32, 0}, // eg typ + {0x20, 4, 16, 0}, // ksr + {0xC0, 0, 1, 0}, // decay alg + {0xC0, 1, 14, 0} // feedback +}; + +static const byte param_table_1[16] = { + 29, 28, 27, 0, + 3, 4, 7, 8, + 13, 16, 17, 20, + 21, 30, 31, 0 +}; + +static const uint16 maxval_table[16] = { + 0x2FF, 0x1F, 0x7, 0x3F, + 0x0F, 0x0F, 0x0F, 0x3, + 0x3F, 0x0F, 0x0F, 0x0F, + 0x3, 0x3E, 0x1F, 0 +}; + +static const uint16 num_steps_table[] = { + 1, 2, 4, 5, + 6, 7, 8, 9, + 10, 12, 14, 16, + 18, 21, 24, 30, + 36, 50, 64, 82, + 100, 136, 160, 192, + 240, 276, 340, 460, + 600, 860, 1200, 1600 +}; + +static const byte note_to_f_num[] = { + 90, 91, 92, 92, 93, 94, 94, 95, + 96, 96, 97, 98, 98, 99, 100, 101, + 101, 102, 103, 104, 104, 105, 106, 107, + 107, 108, 109, 110, 111, 111, 112, 113, + 114, 115, 115, 116, 117, 118, 119, 120, + 121, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, + 143, 145, 146, 147, 148, 149, 150, 151, + 152, 153, 154, 155, 157, 158, 159, 160, + 161, 162, 163, 165, 166, 167, 168, 169, + 171, 172, 173, 174, 176, 177, 178, 180, + 181, 182, 184, 185, 186, 188, 189, 190, + 192, 193, 194, 196, 197, 199, 200, 202, + 203, 205, 206, 208, 209, 211, 212, 214, + 215, 217, 218, 220, 222, 223, 225, 226, + 228, 230, 231, 233, 235, 236, 238, 240, + 242, 243, 245, 247, 249, 251, 252, 254 +}; + +static const byte map_gm_to_fm[128][30] = { + // 0x00 +{ 0xC2, 0xC5, 0x2B, 0x99, 0x58, 0xC2, 0x1F, 0x1E, 0xC8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x23 }, +{ 0x22, 0x53, 0x0E, 0x8A, 0x30, 0x14, 0x06, 0x1D, 0x7A, 0x5C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x06, 0x00, 0x1C, 0x79, 0x40, 0x02, 0x00, 0x4B, 0x79, 0x58, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC2, 0x89, 0x2A, 0x89, 0x49, 0xC2, 0x16, 0x1C, 0xB8, 0x7C, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x23 }, +{ 0xC2, 0x17, 0x3D, 0x6A, 0x00, 0xC4, 0x2E, 0x2D, 0xC9, 0x20, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x06, 0x1E, 0x1C, 0x99, 0x00, 0x02, 0x3A, 0x4C, 0x79, 0x00, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x84, 0x40, 0x3B, 0x5A, 0x6F, 0x81, 0x0E, 0x3B, 0x5A, 0x7F, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x84, 0x40, 0x3B, 0x5A, 0x63, 0x81, 0x00, 0x3B, 0x5A, 0x7F, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x8C, 0x80, 0x05, 0xEA, 0x59, 0x82, 0x0A, 0x3C, 0xAA, 0x64, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x85, 0x40, 0x0D, 0xEC, 0x71, 0x84, 0x58, 0x3E, 0xCB, 0x7C, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x8A, 0xC0, 0x0C, 0xDC, 0x50, 0x88, 0x58, 0x3D, 0xDA, 0x7C, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC9, 0x40, 0x2B, 0x78, 0x42, 0xC2, 0x04, 0x4C, 0x8A, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x1A }, +{ 0x2A, 0x0E, 0x17, 0x89, 0x28, 0x22, 0x0C, 0x1B, 0x09, 0x70, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE7, 0x9B, 0x08, 0x08, 0x26, 0xE2, 0x06, 0x0A, 0x08, 0x70, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC5, 0x05, 0x00, 0xFC, 0x40, 0x84, 0x00, 0x00, 0xDC, 0x50, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x86, 0x40, 0x5D, 0x5A, 0x41, 0x81, 0x00, 0x0B, 0x5A, 0x7F, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, + // 0x10 +{ 0xED, 0x00, 0x7B, 0xC8, 0x40, 0xE1, 0x99, 0x4A, 0xE9, 0x7E, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE8, 0x4F, 0x3A, 0xD7, 0x7C, 0xE2, 0x97, 0x49, 0xF9, 0x7D, 0x05, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE1, 0x10, 0x2F, 0xF7, 0x7D, 0xF3, 0x45, 0x8F, 0xC7, 0x62, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x01, 0x8C, 0x9F, 0xDA, 0x70, 0xE4, 0x50, 0x9F, 0xDA, 0x6A, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x08, 0xD5, 0x9D, 0xA5, 0x45, 0xE2, 0x3F, 0x9F, 0xD6, 0x49, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE5, 0x0F, 0x7D, 0xB8, 0x2E, 0xA2, 0x0F, 0x7C, 0xC7, 0x61, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xF2, 0x2A, 0x9F, 0xDB, 0x01, 0xE1, 0x04, 0x8F, 0xD7, 0x62, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x88, 0x9C, 0x50, 0x64, 0xE2, 0x18, 0x70, 0xC4, 0x7C, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x02, 0xA3, 0x0D, 0xDA, 0x01, 0xC2, 0x35, 0x5D, 0x58, 0x00, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x18 }, +{ 0x42, 0x55, 0x3E, 0xEB, 0x24, 0xD4, 0x08, 0x0D, 0xA9, 0x71, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x18 }, +{ 0xC2, 0x00, 0x2B, 0x17, 0x51, 0xC2, 0x1E, 0x4D, 0x97, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x19 }, +{ 0xC6, 0x01, 0x2D, 0xA7, 0x44, 0xC2, 0x06, 0x0E, 0xA7, 0x79, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC2, 0x0C, 0x06, 0x06, 0x55, 0xC2, 0x3F, 0x09, 0x86, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0A }, +{ 0xC2, 0x2E, 0x4F, 0x77, 0x00, 0xC4, 0x08, 0x0E, 0x98, 0x59, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC2, 0x30, 0x4F, 0xCA, 0x01, 0xC4, 0x0D, 0x0E, 0xB8, 0x7F, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC4, 0x29, 0x4F, 0xCA, 0x03, 0xC8, 0x0D, 0x0C, 0xB7, 0x7D, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0B }, + // 0x20 +{ 0xC2, 0x40, 0x3C, 0x96, 0x58, 0xC4, 0xDE, 0x0E, 0xC7, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x20 }, +{ 0x31, 0x13, 0x2D, 0xD7, 0x3C, 0xE2, 0x18, 0x2E, 0xB8, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x22, 0x86, 0x0D, 0xD7, 0x50, 0xE4, 0x18, 0x5E, 0xB8, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x28 }, +{ 0xF2, 0x0A, 0x0D, 0xD7, 0x40, 0xE4, 0x1F, 0x5E, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xF2, 0x09, 0x4B, 0xD6, 0x48, 0xE4, 0x1F, 0x1C, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x28 }, +{ 0x62, 0x11, 0x0C, 0xE6, 0x3C, 0xE4, 0x1F, 0x0C, 0xC8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x12, 0x3D, 0xE6, 0x34, 0xE4, 0x1F, 0x7D, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x13, 0x3D, 0xE6, 0x34, 0xE4, 0x1F, 0x5D, 0xB8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xA2, 0x40, 0x5D, 0xBA, 0x3F, 0xE2, 0x00, 0x8F, 0xD8, 0x79, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x40, 0x3D, 0xDA, 0x3B, 0xE1, 0x00, 0x7E, 0xD8, 0x7A, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x62, 0x00, 0x6D, 0xFA, 0x5D, 0xE2, 0x00, 0x8F, 0xC8, 0x79, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE1, 0x00, 0x4E, 0xDB, 0x4A, 0xE3, 0x18, 0x6F, 0xE9, 0x7E, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE1, 0x00, 0x4E, 0xDB, 0x66, 0xE2, 0x00, 0x7F, 0xE9, 0x7E, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x02, 0x0F, 0x66, 0xAA, 0x51, 0x02, 0x64, 0x29, 0xF9, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, +{ 0x16, 0x4A, 0x04, 0xBA, 0x39, 0xC2, 0x58, 0x2D, 0xCA, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0x02, 0x00, 0x01, 0x7A, 0x79, 0x02, 0x3F, 0x28, 0xEA, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, + // 0x30 +{ 0x62, 0x53, 0x9C, 0xBA, 0x31, 0x62, 0x5B, 0xAD, 0xC9, 0x55, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xF2, 0x40, 0x6E, 0xDA, 0x49, 0xE2, 0x13, 0x8F, 0xF9, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x40, 0x8F, 0xFA, 0x50, 0xF2, 0x04, 0x7F, 0xFA, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0xA0, 0xCE, 0x5B, 0x02, 0xE2, 0x32, 0x7F, 0xFB, 0x3D, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE6, 0x80, 0x9C, 0x99, 0x42, 0xE2, 0x04, 0x7D, 0x78, 0x60, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xEA, 0xA0, 0xAC, 0x67, 0x02, 0xE2, 0x00, 0x7C, 0x7A, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE7, 0x94, 0xAD, 0xB7, 0x03, 0xE2, 0x00, 0x7C, 0xBA, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC3, 0x3F, 0x4B, 0xE9, 0x7E, 0xC1, 0x3F, 0x9B, 0xF9, 0x7F, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 }, +{ 0xB2, 0x20, 0xAD, 0xE9, 0x00, 0x62, 0x05, 0x8F, 0xC8, 0x68, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xF2, 0x00, 0x8F, 0xFB, 0x50, 0xF6, 0x47, 0x8F, 0xE9, 0x68, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xF2, 0x00, 0xAF, 0x88, 0x58, 0xF2, 0x54, 0x6E, 0xC9, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xF2, 0x2A, 0x9F, 0x98, 0x01, 0xE2, 0x84, 0x4E, 0x78, 0x6C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x02, 0x9F, 0xB8, 0x48, 0x22, 0x89, 0x9F, 0xE8, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x2A, 0x7F, 0xB8, 0x01, 0xE4, 0x00, 0x0D, 0xC5, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x28, 0x8E, 0xE8, 0x01, 0xF2, 0x00, 0x4D, 0xD6, 0x7D, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x62, 0x23, 0x8F, 0xEA, 0x00, 0xF2, 0x00, 0x5E, 0xD9, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, + // 0x40 +{ 0xB4, 0x26, 0x6E, 0x98, 0x01, 0x62, 0x00, 0x7D, 0xC8, 0x7D, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x2E, 0x20, 0xD9, 0x01, 0xF2, 0x0F, 0x90, 0xF8, 0x78, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x28, 0x7E, 0xF8, 0x01, 0xE2, 0x23, 0x8E, 0xE8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xB8, 0x28, 0x9E, 0x98, 0x01, 0x62, 0x00, 0x3D, 0xC8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x62, 0x00, 0x8E, 0xC9, 0x3D, 0xE6, 0x00, 0x7E, 0xD8, 0x68, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x00, 0x5F, 0xF9, 0x48, 0xE6, 0x98, 0x8F, 0xF8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x62, 0x0C, 0x6E, 0xD8, 0x3D, 0x2A, 0x06, 0x7D, 0xD8, 0x58, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x00, 0x7E, 0x89, 0x38, 0xE6, 0x84, 0x80, 0xF8, 0x68, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x80, 0x6C, 0xD9, 0x30, 0xE2, 0x00, 0x8D, 0xC8, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x80, 0x88, 0x48, 0x40, 0xE2, 0x0A, 0x7D, 0xA8, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x00, 0x77, 0xC5, 0x54, 0xE2, 0x00, 0x9E, 0xD7, 0x70, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x80, 0x86, 0xB9, 0x64, 0xE2, 0x05, 0x9F, 0xD7, 0x78, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x00, 0x68, 0x68, 0x56, 0xE2, 0x08, 0x9B, 0xB3, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x00, 0xA6, 0x87, 0x41, 0xE2, 0x0A, 0x7E, 0xC9, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x80, 0x9A, 0xB8, 0x48, 0xE2, 0x00, 0x9E, 0xF9, 0x60, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x80, 0x8E, 0x64, 0x68, 0xE2, 0x28, 0x6F, 0x73, 0x7C, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, + // 0x50 +{ 0xE8, 0x00, 0x7D, 0x99, 0x54, 0xE6, 0x80, 0x80, 0xF8, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE6, 0x00, 0x9F, 0xB9, 0x6D, 0xE1, 0x00, 0x8F, 0xC8, 0x7D, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x00, 0x09, 0x68, 0x4A, 0xE2, 0x2B, 0x9E, 0xF3, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC4, 0x00, 0x99, 0xE8, 0x3B, 0xE2, 0x25, 0x6F, 0x93, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE6, 0x00, 0x6F, 0xDA, 0x69, 0xE2, 0x05, 0x2F, 0xD8, 0x6A, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xEC, 0x60, 0x9D, 0xC7, 0x00, 0xE2, 0x21, 0x7F, 0xC9, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE3, 0x00, 0x0F, 0xF7, 0x7D, 0xE1, 0x3F, 0x0F, 0xA7, 0x01, 0x0D, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0xA9, 0x0F, 0xA8, 0x02, 0xE2, 0x3C, 0x5F, 0xDA, 0x3C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE8, 0x40, 0x0D, 0x89, 0x7D, 0xE2, 0x17, 0x7E, 0xD9, 0x7C, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE1, 0x00, 0xDF, 0x8A, 0x56, 0xE2, 0x5E, 0xCF, 0xBA, 0x7E, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x00, 0x0B, 0x68, 0x60, 0xE2, 0x01, 0x9E, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xEA, 0x00, 0xAE, 0xAB, 0x49, 0xE2, 0x00, 0xAE, 0xBA, 0x6C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xEB, 0x80, 0x8C, 0xCB, 0x3A, 0xE2, 0x86, 0xAF, 0xCA, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE5, 0x40, 0xDB, 0x3B, 0x3C, 0xE2, 0x80, 0xBE, 0xCA, 0x71, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x00, 0x9E, 0xAA, 0x3D, 0xE1, 0x43, 0x0F, 0xBA, 0x7E, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE7, 0x40, 0xEC, 0xCA, 0x44, 0xE2, 0x03, 0xBF, 0xBA, 0x66, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, + // 0x60 +{ 0xEA, 0x00, 0x68, 0xB8, 0x48, 0xE2, 0x0A, 0x8E, 0xB8, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x61, 0x00, 0xBE, 0x99, 0x7E, 0xE3, 0x40, 0xCF, 0xCA, 0x7D, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xCD, 0x00, 0x0B, 0x00, 0x48, 0xC2, 0x58, 0x0C, 0x00, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x1C }, +{ 0xE2, 0x00, 0x0E, 0x00, 0x52, 0xE2, 0x58, 0x5F, 0xD0, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xCC, 0x00, 0x7D, 0xDA, 0x40, 0xC2, 0x00, 0x5E, 0x9B, 0x58, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE9, 0xC0, 0xEE, 0xD8, 0x43, 0xE2, 0x05, 0xDD, 0xAA, 0x70, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xDA, 0x00, 0x8F, 0xAC, 0x4A, 0x22, 0x05, 0x8D, 0x8A, 0x75, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x62, 0x8A, 0xCB, 0x7A, 0x74, 0xE6, 0x56, 0xAF, 0xDB, 0x70, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC2, 0x41, 0xAC, 0x5B, 0x5B, 0xC2, 0x80, 0x0D, 0xCB, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x12 }, +{ 0x75, 0x00, 0x0E, 0xCB, 0x5A, 0xE2, 0x1E, 0x0A, 0xC9, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x10 }, +{ 0x41, 0x00, 0x0E, 0xEA, 0x53, 0xC2, 0x00, 0x08, 0xCA, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 }, +{ 0xC1, 0x40, 0x0C, 0x59, 0x6A, 0xC2, 0x80, 0x3C, 0xAB, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0D }, +{ 0x4B, 0x00, 0x0A, 0xF5, 0x61, 0xC2, 0x19, 0x0C, 0xE9, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 }, +{ 0x62, 0x00, 0x7F, 0xD8, 0x54, 0xEA, 0x00, 0x8F, 0xD8, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE1, 0x00, 0x7F, 0xD9, 0x56, 0xE1, 0x00, 0x8F, 0xD8, 0x7E, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE1, 0x00, 0x7F, 0xD9, 0x56, 0xE1, 0x00, 0x8F, 0xD8, 0x7E, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, + // 0x70 +{ 0xCF, 0x40, 0x09, 0xEA, 0x54, 0xC4, 0x00, 0x0C, 0xDB, 0x64, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xCF, 0x40, 0x0C, 0xAA, 0x54, 0xC4, 0x00, 0x18, 0xF9, 0x64, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xC9, 0x0E, 0x88, 0xD9, 0x3E, 0xC2, 0x08, 0x1A, 0xEA, 0x6C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0x03, 0x00, 0x15, 0x00, 0x64, 0x02, 0x00, 0x08, 0x00, 0x7C, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x01, 0x00, 0x47, 0xD7, 0x6C, 0x01, 0x3F, 0x0C, 0xFB, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, +{ 0x00, 0x00, 0x36, 0x67, 0x7C, 0x01, 0x3F, 0x0E, 0xFA, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0x02, 0x00, 0x36, 0x68, 0x7C, 0x01, 0x3F, 0x0E, 0xFA, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0xCB, 0x00, 0xAF, 0x00, 0x7E, 0xC0, 0x00, 0xC0, 0x06, 0x7F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0F }, +{ 0x05, 0x0D, 0x80, 0xA6, 0x7F, 0x0B, 0x38, 0xA9, 0xD8, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, +{ 0x0F, 0x00, 0x90, 0xFA, 0x68, 0x06, 0x00, 0xA7, 0x39, 0x54, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 }, +{ 0xC9, 0x15, 0xDD, 0xFF, 0x7C, 0x00, 0x00, 0xE7, 0xFC, 0x6C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x38 }, +{ 0x48, 0x3C, 0x30, 0xF6, 0x03, 0x0A, 0x38, 0x97, 0xE8, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, +{ 0x07, 0x80, 0x0B, 0xC8, 0x65, 0x02, 0x3F, 0x0C, 0xEA, 0x7C, 0x0F, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0x00, 0x21, 0x66, 0x40, 0x03, 0x00, 0x3F, 0x47, 0x00, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x08, 0x00, 0x0B, 0x3C, 0x7C, 0x08, 0x3F, 0x06, 0xF3, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x00, 0x3F, 0x4C, 0xFB, 0x00, 0x00, 0x3F, 0x0A, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 } +}; + +static byte gm_percussion_to_fm[39][30] = { +{ 0x1A, 0x3F, 0x15, 0x05, 0x7C, 0x02, 0x21, 0x2B, 0xE4, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 }, +{ 0x11, 0x12, 0x04, 0x07, 0x7C, 0x02, 0x23, 0x0B, 0xE5, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0x0A, 0x3F, 0x0B, 0x01, 0x7C, 0x1F, 0x1C, 0x46, 0xD0, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x01 }, +{ 0x00, 0x3F, 0x0F, 0x00, 0x7C, 0x10, 0x12, 0x07, 0x00, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x0F, 0x3F, 0x0B, 0x00, 0x7C, 0x1F, 0x0F, 0x19, 0xD0, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x00, 0x3F, 0x1F, 0x00, 0x7E, 0x1F, 0x16, 0x07, 0x00, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0x12, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x1F, 0x4A, 0xD9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0xCF, 0x7F, 0x08, 0xFF, 0x7E, 0x00, 0xC7, 0x2D, 0xF7, 0x73, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x12, 0x3F, 0x05, 0x06, 0x7C, 0x43, 0x21, 0x0C, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0xCF, 0x7F, 0x08, 0xCF, 0x7E, 0x00, 0x45, 0x2A, 0xF8, 0x4B, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0C }, +{ 0x12, 0x3F, 0x06, 0x17, 0x7C, 0x03, 0x27, 0x0B, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0xCF, 0x7F, 0x08, 0xCD, 0x7E, 0x00, 0x40, 0x1A, 0x69, 0x63, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0C }, +{ 0x13, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x17, 0x0A, 0xD9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0x15, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x21, 0x0C, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0xCF, 0x3F, 0x2B, 0xFB, 0x7E, 0xC0, 0x1E, 0x1A, 0xCA, 0x7F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x10 }, +{ 0x17, 0x3F, 0x04, 0x09, 0x7C, 0x03, 0x22, 0x0D, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0xCF, 0x3F, 0x0F, 0x5E, 0x7C, 0xC6, 0x13, 0x00, 0xCA, 0x7F, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0xCF, 0x3F, 0x7E, 0x9D, 0x7C, 0xC8, 0xC0, 0x0A, 0xBA, 0x74, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 }, +{ 0xCF, 0x3F, 0x4D, 0x9F, 0x7C, 0xC6, 0x00, 0x08, 0xDA, 0x5B, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, +{ 0xCF, 0x3F, 0x5D, 0xAA, 0x7A, 0xC0, 0xA4, 0x67, 0x99, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xCF, 0x3F, 0x4A, 0xFD, 0x7C, 0xCF, 0x00, 0x59, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x0F, 0x18, 0x0A, 0xFA, 0x57, 0x06, 0x07, 0x06, 0x39, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xCF, 0x3F, 0x2B, 0xFC, 0x7C, 0xCC, 0xC6, 0x0B, 0xEA, 0x7F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x10 }, +{ 0x05, 0x1A, 0x04, 0x00, 0x7C, 0x12, 0x10, 0x0C, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 }, +{ 0x04, 0x19, 0x04, 0x00, 0x7C, 0x12, 0x10, 0x2C, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, +{ 0x04, 0x0A, 0x04, 0x00, 0x6C, 0x01, 0x07, 0x0D, 0xFA, 0x74, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 }, +{ 0x15, 0x14, 0x05, 0x00, 0x7D, 0x01, 0x07, 0x5C, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0x10, 0x10, 0x05, 0x08, 0x7C, 0x01, 0x08, 0x0D, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0x11, 0x00, 0x06, 0x87, 0x7F, 0x02, 0x40, 0x09, 0x59, 0x68, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x08 }, +{ 0x13, 0x26, 0x04, 0x6A, 0x7F, 0x01, 0x00, 0x08, 0x5A, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x08 }, +{ 0xCF, 0x4E, 0x0C, 0xAA, 0x50, 0xC4, 0x00, 0x18, 0xF9, 0x54, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xCF, 0x4E, 0x0C, 0xAA, 0x50, 0xC3, 0x00, 0x18, 0xF8, 0x54, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xCB, 0x3F, 0x8F, 0x00, 0x7E, 0xC5, 0x00, 0x98, 0xD6, 0x5F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0D }, +{ 0x0C, 0x18, 0x87, 0xB3, 0x7F, 0x19, 0x10, 0x55, 0x75, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x05, 0x11, 0x15, 0x00, 0x64, 0x02, 0x08, 0x08, 0x00, 0x5C, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x04, 0x08, 0x15, 0x00, 0x48, 0x01, 0x08, 0x08, 0x00, 0x60, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xDA, 0x00, 0x53, 0x30, 0x68, 0x07, 0x1E, 0x49, 0xC4, 0x7E, 0x03, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x1C, 0x00, 0x07, 0xBC, 0x6C, 0x0C, 0x14, 0x0B, 0x6A, 0x7E, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0x0A, 0x0E, 0x7F, 0x00, 0x7D, 0x13, 0x20, 0x28, 0x03, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 } +}; + +static const byte gm_percussion_lookup[128] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0xFF, 0xFF, 0x17, 0x18, 0x19, 0x1A, + 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x21, 0x22, 0x23, 0xFF, 0xFF, + 0x24, 0x25, 0xFF, 0xFF, 0xFF, 0x26, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +static byte lookup_table[64][32]; + +static const byte volume_table[] = { + 0, 4, 7, 11, + 13, 16, 18, 20, + 22, 24, 26, 27, + 29, 30, 31, 33, + 34, 35, 36, 37, + 38, 39, 40, 41, + 42, 43, 44, 44, + 45, 46, 47, 47, + 48, 49, 49, 50, + 51, 51, 52, 53, + 53, 54, 54, 55, + 55, 56, 56, 57, + 57, 58, 58, 59, + 59, 60, 60, 60, + 61, 61, 62, 62, + 62, 63, 63, 63 +}; + +static int lookup_volume(int a, int b) { + if (b == 0) + return 0; + + if (b == 31) + return a; + + if (a < -63 || a > 63) { + return b * (a + 1) >> 5; + } + + if (b < 0) { + if (a < 0) { + return lookup_table[-a][-b]; + } else { + return -lookup_table[a][-b]; + } + } else { + if (a < 0) { + return -lookup_table[-a][b]; + } else { + return lookup_table[a][b]; + } + } +} + +static void create_lookup_table() { + int i, j; + int sum; + + for (i = 0; i < 64; i++) { + sum = i; + for (j = 0; j < 32; j++) { + lookup_table[i][j] = sum >> 5; + sum += i; + } + } + for (i = 0; i < 64; i++) + lookup_table[i][0] = 0; +} + +//////////////////////////////////////// +// +// AdLib MIDI driver +// +//////////////////////////////////////// + +class MidiDriver_ADLIB : public MidiDriver_Emulated { + friend class AdLibPart; + friend class AdLibPercussionChannel; + +public: + MidiDriver_ADLIB(Audio::Mixer *mixer); + + int open(); + void close(); + void send(uint32 b); + void send(byte channel, uint32 b); // Supports higher than channel 15 + uint32 property(int prop, uint32 param); + + void setPitchBendRange(byte channel, uint range); + void sysEx_customInstrument(byte channel, uint32 type, const byte *instr); + + MidiChannel *allocateChannel(); + MidiChannel *getPercussionChannel() { return &_percussion; } // Percussion partially supported + + + // AudioStream API + bool isStereo() const { return false; } + int getRate() const { return _mixer->getOutputRate(); } + +private: + bool _scummSmallHeader; // FIXME: This flag controls a special mode for SCUMM V3 games + + FM_OPL *_opl; + byte *_adlib_reg_cache; + + int _adlib_timer_counter; + + uint16 channel_table_2[9]; + int _voice_index; + int _timer_p; + int _timer_q; + uint16 curnote_table[9]; + AdLibVoice _voices[9]; + AdLibPart _parts[32]; + AdLibPercussionChannel _percussion; + + void generateSamples(int16 *buf, int len); + void onTimer(); + void part_key_on(AdLibPart *part, AdLibInstrument *instr, byte note, byte velocity); + void part_key_off(AdLibPart *part, byte note); + + void adlib_key_off(int chan); + void adlib_note_on(int chan, byte note, int mod); + void adlib_note_on_ex(int chan, byte note, int mod); + int adlib_get_reg_value_param(int chan, byte data); + void adlib_setup_channel(int chan, AdLibInstrument * instr, byte vol_1, byte vol_2); + byte adlib_get_reg_value(byte reg) { + return _adlib_reg_cache[reg]; + } + void adlib_set_param(int channel, byte param, int value); + void adlib_key_onoff(int channel); + void adlib_write(byte reg, byte value); + void adlib_playnote(int channel, int note); + + AdLibVoice *allocate_voice(byte pri); + + void mc_off(AdLibVoice * voice); + + static void link_mc(AdLibPart *part, AdLibVoice *voice); + void mc_inc_stuff(AdLibVoice *voice, Struct10 * s10, Struct11 * s11); + void mc_init_stuff(AdLibVoice *voice, Struct10 * s10, Struct11 * s11, byte flags, + InstrumentExtra * ie); + + void struct10_init(Struct10 * s10, InstrumentExtra * ie); + static byte struct10_ontimer(Struct10 * s10, Struct11 * s11); + static void struct10_setup(Struct10 * s10); + static int random_nr(int a); + void mc_key_on(AdLibVoice *voice, AdLibInstrument *instr, byte note, byte velocity); +}; + +// MidiChannel method implementations + +void AdLibPart::init(MidiDriver_ADLIB *owner, byte channel) { + _owner = owner; + _channel = channel; + _pri_eff = 127; + programChange(0); +} + +MidiDriver *AdLibPart::device() { + return _owner; +} + +void AdLibPart::send(uint32 b) { + _owner->send(_channel, b); +} + +void AdLibPart::noteOff(byte note) { +#ifdef DEBUG_ADLIB + debug(6, "%10d: noteOff(%d)", tick, note); +#endif + _owner->part_key_off(this, note); +} + +void AdLibPart::noteOn(byte note, byte velocity) { +#ifdef DEBUG_ADLIB + debug(6, "%10d: noteOn(%d,%d)", tick, note, velocity); +#endif + _owner->part_key_on(this, &_part_instr, note, velocity); +} + +void AdLibPart::programChange(byte program) { + if (program > 127) + return; + + uint i; + uint count = 0; + for (i = 0; i < ARRAYSIZE(map_gm_to_fm[0]); ++i) + count += map_gm_to_fm[program][i]; + if (!count) + warning("No AdLib instrument defined for GM program %d", (int) program); + _program = program; + memcpy(&_part_instr, &map_gm_to_fm[program], sizeof(AdLibInstrument)); +} + +void AdLibPart::pitchBend(int16 bend) { + AdLibVoice *voice; + + _pitchbend = bend; + for (voice = _voice; voice; voice = voice->_next) { + _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff, + (_pitchbend * _pitchbend_factor >> 6) + _detune_eff); + } +} + +void AdLibPart::controlChange(byte control, byte value) { + switch (control) { + case 0: + case 32: + break; // Bank select. Not supported + case 1: modulationWheel(value); break; + case 7: volume(value); break; + case 10: break; // Pan position. Not supported. + case 16: pitchBendFactor(value); break; + case 17: detune(value); break; + case 18: priority(value); break; + case 64: sustain(value > 0); break; + case 91: break; // Effects level. Not supported. + case 93: break; // Chorus level. Not supported. + case 119: break; // Unknown, used in Simon the Sorcerer 2 + case 121: // reset all controllers + modulationWheel(0); + pitchBendFactor(0); + detune(0); + sustain(0); + break; + case 123: allNotesOff(); break; + default: + warning("AdLib: Unknown control change message %d (%d)", (int) control, (int)value); + } +} + +void AdLibPart::modulationWheel(byte value) { + AdLibVoice *voice; + + _modwheel = value; + for (voice = _voice; voice; voice = voice->_next) { + if (voice->_s10a.active && voice->_s11a.flag0x40) + voice->_s10a.modwheel = _modwheel >> 2; + if (voice->_s10b.active && voice->_s11b.flag0x40) + voice->_s10b.modwheel = _modwheel >> 2; + } +} + +void AdLibPart::volume(byte value) { + AdLibVoice *voice; + + _vol_eff = value; + for (voice = _voice; voice; voice = voice->_next) { + _owner->adlib_set_param(voice->_channel, 0, volume_table[lookup_table[voice->_vol_2][_vol_eff >> 2]]); + if (voice->_twochan) { + _owner->adlib_set_param(voice->_channel, 13, volume_table[lookup_table[voice->_vol_1][_vol_eff >> 2]]); + } + } +} + +void AdLibPart::pitchBendFactor(byte value) { + AdLibVoice *voice; + + _pitchbend_factor = value; + for (voice = _voice; voice; voice = voice->_next) { + _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff, + (_pitchbend * _pitchbend_factor >> 6) + _detune_eff); + } +} + +void AdLibPart::detune(byte value) { + AdLibVoice *voice; + + _detune_eff = value; + for (voice = _voice; voice; voice = voice->_next) { + _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff, + (_pitchbend * _pitchbend_factor >> 6) + _detune_eff); + } +} + +void AdLibPart::priority(byte value) { + _pri_eff = value; +} + +void AdLibPart::sustain(bool value) { + AdLibVoice *voice; + + _pedal = value; + if (!value) { + for (voice = _voice; voice; voice = voice->_next) { + if (voice->_waitforpedal) + _owner->mc_off(voice); + } + } +} + +void AdLibPart::allNotesOff() { + while (_voice) + _owner->mc_off(_voice); +} + +void AdLibPart::sysEx_customInstrument(uint32 type, const byte *instr) { + if (type == 'ADL ') { + AdLibInstrument *i = &_part_instr; + memcpy(i, instr, sizeof(AdLibInstrument)); + } +} + +// MidiChannel method implementations for percussion + +AdLibPercussionChannel::~AdLibPercussionChannel() { + for (int i = 0; i < ARRAYSIZE(_customInstruments); ++i) { + delete _customInstruments[i]; + } +} + +void AdLibPercussionChannel::init(MidiDriver_ADLIB *owner, byte channel) { + AdLibPart::init(owner, channel); + _pri_eff = 0; + _vol_eff = 127; + + // Initialize the custom instruments data + memset(_notes, 0, sizeof(_notes)); + memset(_customInstruments, 0, sizeof(_customInstruments)); +} + +void AdLibPercussionChannel::noteOff(byte note) { + // Jamieson630: Unless I run into a specific instrument that + // may require a key off, I'm going to ignore this message. + // The rationale is that a percussion instrument should + // fade out of its own accord, and the AdLib instrument + // definitions used should follow this rule. Since + // percussion voices are allocated at the lowest priority + // anyway, we know that "hanging" percussion sounds will + // not prevent later musical instruments (or even other + // percussion sounds) from playing. +/* + _owner->part_key_off(this, note); +*/ +} + +void AdLibPercussionChannel::noteOn(byte note, byte velocity) { + AdLibInstrument *inst = NULL; + + // The custom instruments have priority over the default mapping + inst = _customInstruments[note]; + if (inst) + note = _notes[note]; + + if (!inst) { + // Use the default GM to FM mapping as a fallback as a fallback + byte key = gm_percussion_lookup[note]; + if (key != 0xFF) + inst = (AdLibInstrument *)&gm_percussion_to_fm[key]; + } + + if (!inst) { + debug(2, "No instrument FM definition for GM percussion key %d", (int)note); + return; + } + + _owner->part_key_on(this, inst, note, velocity); +} + +void AdLibPercussionChannel::sysEx_customInstrument(uint32 type, const byte *instr) { + if (type == 'ADLP') { + byte note = instr[0]; + _notes[note] = instr[1]; + + // Allocate memory for the new instruments + if (!_customInstruments[note]) { + _customInstruments[note] = new AdLibInstrument; + } + + // Save the new instrument data + _customInstruments[note]->mod_characteristic = instr[2]; + _customInstruments[note]->mod_scalingOutputLevel = instr[3]; + _customInstruments[note]->mod_attackDecay = instr[4]; + _customInstruments[note]->mod_sustainRelease = instr[5]; + _customInstruments[note]->mod_waveformSelect = instr[6]; + _customInstruments[note]->car_characteristic = instr[7]; + _customInstruments[note]->car_scalingOutputLevel = instr[8]; + _customInstruments[note]->car_attackDecay = instr[9]; + _customInstruments[note]->car_sustainRelease = instr[10]; + _customInstruments[note]->car_waveformSelect = instr[11]; + _customInstruments[note]->feedback = instr[12]; + } +} + +// MidiDriver method implementations + +MidiDriver_ADLIB::MidiDriver_ADLIB(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer) { + uint i; + + _scummSmallHeader = false; + + _adlib_reg_cache = 0; + + _adlib_timer_counter = 0; + _voice_index = 0; + for (i = 0; i < ARRAYSIZE(curnote_table); ++i) { + curnote_table[i] = 0; + } + + for (i = 0; i < ARRAYSIZE(_parts); ++i) { + _parts[i].init(this, i + ((i >= 9) ? 1 : 0)); + } + _percussion.init(this, 9); + _timer_p = 0xD69; + _timer_q = 0x411B; +} + +int MidiDriver_ADLIB::open() { + if (_isOpen) + return MERR_ALREADY_OPEN; + + MidiDriver_Emulated::open(); + + int i; + AdLibVoice *voice; + + for (i = 0, voice = _voices; i != ARRAYSIZE(_voices); i++, voice++) { + voice->_channel = i; + voice->_s11a.s10 = &voice->_s10b; + voice->_s11b.s10 = &voice->_s10a; + } + + _adlib_reg_cache = (byte *)calloc(256, 1); + + _opl = makeAdLibOPL(getRate()); + + adlib_write(1, 0x20); + adlib_write(8, 0x40); + adlib_write(0xBD, 0x00); + create_lookup_table(); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + + return 0; +} + +void MidiDriver_ADLIB::close() { + if (!_isOpen) + return; + _isOpen = false; + + _mixer->stopHandle(_mixerSoundHandle); + + uint i; + for (i = 0; i < ARRAYSIZE(_voices); ++i) { + if (_voices[i]._part) + mc_off(&_voices[i]); + } + + // Turn off the OPL emulation + OPLDestroy(_opl); +// YM3812Shutdown(); + + free(_adlib_reg_cache); +} + +void MidiDriver_ADLIB::send(uint32 b) { + send(b & 0xF, b & 0xFFFFFFF0); +} + +void MidiDriver_ADLIB::send(byte chan, uint32 b) { + //byte param3 = (byte) ((b >> 24) & 0xFF); + byte param2 = (byte) ((b >> 16) & 0xFF); + byte param1 = (byte) ((b >> 8) & 0xFF); + byte cmd = (byte) (b & 0xF0); + + AdLibPart *part; + if (chan == 9) + part = &_percussion; + else + part = &_parts[chan]; + + switch (cmd) { + case 0x80:// Note Off + part->noteOff(param1); + break; + case 0x90: // Note On + part->noteOn(param1, param2); + break; + case 0xA0: // Aftertouch + break; // Not supported. + case 0xB0: // Control Change + part->controlChange(param1, param2); + break; + case 0xC0: // Program Change + part->programChange(param1); + break; + case 0xD0: // Channel Pressure + break; // Not supported. + case 0xE0: // Pitch Bend + part->pitchBend((param1 | (param2 << 7)) - 0x2000); + break; + case 0xF0: // SysEx + // We should never get here! SysEx information has to be + // sent via high-level semantic methods. + warning("MidiDriver_ADLIB: Receiving SysEx command on a send() call"); + break; + + default: + warning("MidiDriver_ADLIB: Unknown send() command 0x%02X", cmd); + } +} + +uint32 MidiDriver_ADLIB::property(int prop, uint32 param) { + switch (prop) { + case PROP_OLD_ADLIB: // Older games used a different operator volume algorithm + _scummSmallHeader = (param > 0); + if (_scummSmallHeader) { + _timer_p = 473; + _timer_q = 1000; + } else { + _timer_p = 0xD69; + _timer_q = 0x411B; + } + return 1; + } + + return 0; +} + +void MidiDriver_ADLIB::setPitchBendRange(byte channel, uint range) { + AdLibVoice *voice; + AdLibPart *part = &_parts[channel]; + + part->_pitchbend_factor = range; + for (voice = part->_voice; voice; voice = voice->_next) { + adlib_note_on(voice->_channel, voice->_note + part->_transpose_eff, + (part->_pitchbend * part->_pitchbend_factor >> 6) + part->_detune_eff); + } +} + +void MidiDriver_ADLIB::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) { + _parts[channel].sysEx_customInstrument(type, instr); +} + +MidiChannel *MidiDriver_ADLIB::allocateChannel() { + AdLibPart *part; + uint i; + + for (i = 0; i < ARRAYSIZE(_parts); ++i) { + part = &_parts[i]; + if (!part->_allocated) { + part->allocate(); + return part; + } + } + return NULL; +} + +// All the code brought over from IMuseAdLib + +void MidiDriver_ADLIB::adlib_write(byte reg, byte value) { + if (_adlib_reg_cache[reg] == value) + return; +#ifdef DEBUG_ADLIB + debug(6, "%10d: adlib_write[%x] = %x", tick, reg, value); +#endif + _adlib_reg_cache[reg] = value; + + OPLWriteReg(_opl, reg, value); +} + +void MidiDriver_ADLIB::generateSamples(int16 *data, int len) { + memset(data, 0, sizeof(int16) * len); + YM3812UpdateOne(_opl, data, len); +} + +void MidiDriver_ADLIB::onTimer() { + AdLibVoice *voice; + int i; + + _adlib_timer_counter += _timer_p; + while (_adlib_timer_counter >= _timer_q) { + _adlib_timer_counter -= _timer_q; +#ifdef DEBUG_ADLIB + tick++; +#endif + voice = _voices; + for (i = 0; i != ARRAYSIZE(_voices); i++, voice++) { + if (!voice->_part) + continue; + if (voice->_duration && (voice->_duration -= 0x11) <= 0) { + mc_off(voice); + return; + } + if (voice->_s10a.active) { + mc_inc_stuff(voice, &voice->_s10a, &voice->_s11a); + } + if (voice->_s10b.active) { + mc_inc_stuff(voice, &voice->_s10b, &voice->_s11b); + } + } + } +} + +void MidiDriver_ADLIB::mc_off(AdLibVoice *voice) { + AdLibVoice *tmp; + + adlib_key_off(voice->_channel); + + tmp = voice->_prev; + + if (voice->_next) + voice->_next->_prev = tmp; + if (tmp) + tmp->_next = voice->_next; + else + voice->_part->_voice = voice->_next; + voice->_part = NULL; +} + +void MidiDriver_ADLIB::mc_inc_stuff(AdLibVoice *voice, Struct10 *s10, Struct11 *s11) { + byte code; + AdLibPart *part = voice->_part; + + code = struct10_ontimer(s10, s11); + + if (code & 1) { + switch (s11->param) { + case 0: + voice->_vol_2 = s10->start_value + s11->modify_val; + if (!_scummSmallHeader) { + adlib_set_param(voice->_channel, 0, + volume_table[lookup_table[voice->_vol_2] + [part->_vol_eff >> 2]]); + } else { + adlib_set_param(voice->_channel, 0, voice->_vol_2); + } + break; + case 13: + voice->_vol_1 = s10->start_value + s11->modify_val; + if (voice->_twochan && !_scummSmallHeader) { + adlib_set_param(voice->_channel, 13, + volume_table[lookup_table[voice->_vol_1] + [part->_vol_eff >> 2]]); + } else { + adlib_set_param(voice->_channel, 13, voice->_vol_1); + } + break; + case 30: + s11->s10->modwheel = (char)s11->modify_val; + break; + case 31: + s11->s10->unk3 = (char)s11->modify_val; + break; + default: + adlib_set_param(voice->_channel, s11->param, + s10->start_value + s11->modify_val); + break; + } + } + + if (code & 2 && s11->flag0x10) + adlib_key_onoff(voice->_channel); +} + +void MidiDriver_ADLIB::adlib_key_off(int chan){ + byte reg = chan + 0xB0; + adlib_write(reg, adlib_get_reg_value(reg) & ~0x20); +} + +byte MidiDriver_ADLIB::struct10_ontimer(Struct10 *s10, Struct11 *s11) { + byte result = 0; + int i; + + if (s10->count && (s10->count -= 17) <= 0) { + s10->active = 0; + return 0; + } + + i = s10->cur_val + s10->speed_hi; + s10->speed_lo_counter += s10->speed_lo; + if (s10->speed_lo_counter >= s10->speed_lo_max) { + s10->speed_lo_counter -= s10->speed_lo_max; + i += s10->direction; + } + if (s10->cur_val != i || s10->modwheel != s10->modwheel_last) { + s10->cur_val = i; + s10->modwheel_last = s10->modwheel; + i = lookup_volume(i, s10->modwheel_last); + if (i != s11->modify_val) { + s11->modify_val = i; + result = 1; + } + } + + if (!--s10->num_steps) { + s10->active++; + if (s10->active > 4) { + if (s10->loop) { + s10->active = 1; + result |= 2; + struct10_setup(s10); + } else { + s10->active = 0; + } + } else { + struct10_setup(s10); + } + } + + return result; +} + +void MidiDriver_ADLIB::adlib_set_param(int channel, byte param, int value) { + const AdLibSetParams *as; + byte reg; + + assert(channel >= 0 && channel < 9); + + if (param <= 12) { + reg = channel_mappings_2[channel]; + } else if (param <= 25) { + param -= 13; + reg = channel_mappings[channel]; + } else if (param <= 27) { + param -= 13; + reg = channel; + } else if (param == 28 || param == 29) { + if (param == 28) + value -= 15; + else + value -= 383; + value <<= 4; + channel_table_2[channel] = value; + adlib_playnote(channel, curnote_table[channel] + value); + return; + } else { + return; + } + + as = &adlib_setparam_table[param]; + if (as->d) + value = as->d - value; + reg += as->a; + adlib_write(reg, (adlib_get_reg_value(reg) & ~as->c) | (((byte)value) << as->b)); +} + +void MidiDriver_ADLIB::adlib_key_onoff(int channel) { + byte val; + byte reg = channel + 0xB0; + assert(channel >= 0 && channel < 9); + + val = adlib_get_reg_value(reg); + adlib_write(reg, val & ~0x20); + adlib_write(reg, val | 0x20); +} + +void MidiDriver_ADLIB::struct10_setup(Struct10 *s10) { + int b, c, d, e, f, g, h; + byte t; + + b = s10->unk3; + f = s10->active - 1; + + t = s10->table_a[f]; + e = num_steps_table[lookup_table[t & 0x7F][b]]; + if (t & 0x80) { + e = random_nr(e); + } + if (e == 0) + e++; + + s10->num_steps = s10->speed_lo_max = e; + + if (f != 2) { + c = s10->max_value; + g = s10->start_value; + t = s10->table_b[f]; + d = lookup_volume(c, (t & 0x7F) - 31); + if (t & 0x80) { + d = random_nr(d); + } + if (d + g > c) { + h = c - g; + } else { + h = d; + if (d + g < 0) + h = -g; + } + h -= s10->cur_val; + } else { + h = 0; + } + + s10->speed_hi = h / e; + if (h < 0) { + h = -h; + s10->direction = -1; + } else { + s10->direction = 1; + } + + s10->speed_lo = h % e; + s10->speed_lo_counter = 0; +} + +void MidiDriver_ADLIB::adlib_playnote(int channel, int note) { + byte old, oct, notex; + int note2; + int i; + + note2 = (note >> 7) - 4; + note2 = (note2 < 128) ? note2 : 0; + + oct = (note2 / 12); + if (oct > 7) + oct = 7 << 2; + else + oct <<= 2; + notex = note2 % 12 + 3; + + old = adlib_get_reg_value(channel + 0xB0); + if (old & 0x20) { + old &= ~0x20; + if (oct > old) { + if (notex < 6) { + notex += 12; + oct -= 4; + } + } else if (oct < old) { + if (notex > 11) { + notex -= 12; + oct += 4; + } + } + } + + i = (notex << 3) + ((note >> 4) & 0x7); + adlib_write(channel + 0xA0, note_to_f_num[i]); + adlib_write(channel + 0xB0, oct | 0x20); +} + +int MidiDriver_ADLIB::random_nr(int a) { + static byte _rand_seed = 1; + if (_rand_seed & 1) { + _rand_seed >>= 1; + _rand_seed ^= 0xB8; + } else { + _rand_seed >>= 1; + } + return _rand_seed * a >> 8; +} + +void MidiDriver_ADLIB::part_key_off(AdLibPart *part, byte note) { + AdLibVoice *voice; + + for (voice = part->_voice; voice; voice = voice->_next) { + if (voice->_note == note) { + if (part->_pedal) + voice->_waitforpedal = true; + else + mc_off(voice); + } + } +} + +void MidiDriver_ADLIB::part_key_on(AdLibPart *part, AdLibInstrument *instr, byte note, byte velocity) { + AdLibVoice *voice; + + voice = allocate_voice(part->_pri_eff); + if (!voice) + return; + + link_mc(part, voice); + mc_key_on(voice, instr, note, velocity); +} + +AdLibVoice *MidiDriver_ADLIB::allocate_voice(byte pri) { + AdLibVoice *ac, *best = NULL; + int i; + + for (i = 0; i < 9; i++) { + if (++_voice_index >= 9) + _voice_index = 0; + ac = &_voices[_voice_index]; + if (!ac->_part) + return ac; + if (!ac->_next) { + if (ac->_part->_pri_eff <= pri) { + pri = ac->_part->_pri_eff; + best = ac; + } + } + } + + /* SCUMM V3 games don't have note priorities, first comes wins. */ + if (_scummSmallHeader) + return NULL; + + if (best) + mc_off(best); + return best; +} + +void MidiDriver_ADLIB::link_mc(AdLibPart *part, AdLibVoice *voice) { + voice->_part = part; + voice->_next = (AdLibVoice *)part->_voice; + part->_voice = voice; + voice->_prev = NULL; + + if (voice->_next) + voice->_next->_prev = voice; +} + +void MidiDriver_ADLIB::mc_key_on(AdLibVoice *voice, AdLibInstrument *instr, byte note, byte velocity) { + AdLibPart *part = voice->_part; + int c; + byte vol_1, vol_2; + + voice->_twochan = instr->feedback & 1; + voice->_note = note; + voice->_waitforpedal = false; + voice->_duration = instr->duration; + if (voice->_duration != 0) + voice->_duration *= 63; + + if (!_scummSmallHeader) + vol_1 = (instr->mod_scalingOutputLevel & 0x3F) + lookup_table[velocity >> 1][instr->mod_waveformSelect >> 2]; + else + vol_1 = 0x3f - (instr->mod_scalingOutputLevel & 0x3F); + if (vol_1 > 0x3F) + vol_1 = 0x3F; + voice->_vol_1 = vol_1; + + if (!_scummSmallHeader) + vol_2 = (instr->car_scalingOutputLevel & 0x3F) + lookup_table[velocity >> 1][instr->car_waveformSelect >> 2]; + else + vol_2 = 0x3f - (instr->car_scalingOutputLevel & 0x3F); + if (vol_2 > 0x3F) + vol_2 = 0x3F; + voice->_vol_2 = vol_2; + + c = part->_vol_eff >> 2; + + if (!_scummSmallHeader) { + vol_2 = volume_table[lookup_table[vol_2][c]]; + if (voice->_twochan) + vol_1 = volume_table[lookup_table[vol_1][c]]; + } + + adlib_setup_channel(voice->_channel, instr, vol_1, vol_2); + adlib_note_on_ex(voice->_channel, part->_transpose_eff + note, part->_detune_eff + (part->_pitchbend * part->_pitchbend_factor >> 6)); + + if (instr->flags_a & 0x80) { + mc_init_stuff(voice, &voice->_s10a, &voice->_s11a, instr->flags_a, &instr->extra_a); + } else { + voice->_s10a.active = 0; + } + + if (instr->flags_b & 0x80) { + mc_init_stuff(voice, &voice->_s10b, &voice->_s11b, instr->flags_b, &instr->extra_b); + } else { + voice->_s10b.active = 0; + } +} + +void MidiDriver_ADLIB::adlib_setup_channel(int chan, AdLibInstrument *instr, byte vol_1, byte vol_2) { + byte channel; + + assert(chan >= 0 && chan < 9); + + channel = channel_mappings[chan]; + adlib_write(channel + 0x20, instr->mod_characteristic); + adlib_write(channel + 0x40, (instr->mod_scalingOutputLevel | 0x3F) - vol_1 ); + adlib_write(channel + 0x60, 0xff & (~instr->mod_attackDecay)); + adlib_write(channel + 0x80, 0xff & (~instr->mod_sustainRelease)); + adlib_write(channel + 0xE0, instr->mod_waveformSelect); + + channel = channel_mappings_2[chan]; + adlib_write(channel + 0x20, instr->car_characteristic); + adlib_write(channel + 0x40, (instr->car_scalingOutputLevel | 0x3F) - vol_2 ); + adlib_write(channel + 0x60, 0xff & (~instr->car_attackDecay)); + adlib_write(channel + 0x80, 0xff & (~instr->car_sustainRelease)); + adlib_write(channel + 0xE0, instr->car_waveformSelect); + + adlib_write((byte)chan + 0xC0, instr->feedback); +} + +void MidiDriver_ADLIB::adlib_note_on_ex(int chan, byte note, int mod) +{ + int code; + assert(chan >= 0 && chan < 9); + code = (note << 7) + mod; + curnote_table[chan] = code; + channel_table_2[chan] = 0; + adlib_playnote(chan, code); +} + +void MidiDriver_ADLIB::mc_init_stuff(AdLibVoice *voice, Struct10 * s10, + Struct11 * s11, byte flags, InstrumentExtra * ie) { + AdLibPart *part = voice->_part; + s11->modify_val = 0; + s11->flag0x40 = flags & 0x40; + s10->loop = flags & 0x20; + s11->flag0x10 = flags & 0x10; + s11->param = param_table_1[flags & 0xF]; + s10->max_value = maxval_table[flags & 0xF]; + s10->unk3 = 31; + if (s11->flag0x40) { + s10->modwheel = part->_modwheel >> 2; + } else { + s10->modwheel = 31; + } + + switch (s11->param) { + case 0: + s10->start_value = voice->_vol_2; + break; + case 13: + s10->start_value = voice->_vol_1; + break; + case 30: + s10->start_value = 31; + s11->s10->modwheel = 0; + break; + case 31: + s10->start_value = 0; + s11->s10->unk3 = 0; + break; + default: + s10->start_value = adlib_get_reg_value_param(voice->_channel, s11->param); + } + + struct10_init(s10, ie); +} + +void MidiDriver_ADLIB::struct10_init(Struct10 *s10, InstrumentExtra *ie) { + s10->active = 1; + if (!_scummSmallHeader) { + s10->cur_val = 0; + } else { + s10->cur_val = s10->start_value; + s10->start_value = 0; + } + s10->modwheel_last = 31; + s10->count = ie->a; + if (s10->count) + s10->count *= 63; + s10->table_a[0] = ie->b; + s10->table_a[1] = ie->d; + s10->table_a[2] = ie->f; + s10->table_a[3] = ie->g; + + s10->table_b[0] = ie->c; + s10->table_b[1] = ie->e; + s10->table_b[2] = 0; + s10->table_b[3] = ie->h; + + struct10_setup(s10); +} + +int MidiDriver_ADLIB::adlib_get_reg_value_param(int chan, byte param) { + const AdLibSetParams *as; + byte val; + byte channel; + + assert(chan >= 0 && chan < 9); + + if (param <= 12) { + channel = channel_mappings_2[chan]; + } else if (param <= 25) { + param -= 13; + channel = channel_mappings[chan]; + } else if (param <= 27) { + param -= 13; + channel = chan; + } else if (param == 28) { + return 0xF; + } else if (param == 29) { + return 0x17F; + } else { + return 0; + } + + as = &adlib_setparam_table[param]; + val = adlib_get_reg_value(channel + as->a); + val &= as->c; + val >>= as->b; + if (as->d) + val = as->d - val; + + return val; +} + +void MidiDriver_ADLIB::adlib_note_on(int chan, byte note, int mod) { + int code; + assert(chan >= 0 && chan < 9); + code = (note << 7) + mod; + curnote_table[chan] = code; + adlib_playnote(chan, (int16) channel_table_2[chan] + code); +} + + +// Plugin interface + +class AdLibEmuMusicPlugin : public MusicPluginObject { +public: + const char *getName() const { + return _s("AdLib Emulator"); + } + + const char *getId() const { + return "adlib"; + } + + MusicDevices getDevices() const; + Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const; +}; + +MusicDevices AdLibEmuMusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_ADLIB)); + return devices; +} + +Common::Error AdLibEmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const { + *mididriver = new MidiDriver_ADLIB(g_system->getMixer()); + + return Common::kNoError; +} + +//#if PLUGIN_ENABLED_DYNAMIC(ADLIB) + //REGISTER_PLUGIN_DYNAMIC(ADLIB, PLUGIN_TYPE_MUSIC, AdLibEmuMusicPlugin); +//#else + REGISTER_PLUGIN_STATIC(ADLIB, PLUGIN_TYPE_MUSIC, AdLibEmuMusicPlugin); +//#endif diff --git a/audio/softsynth/appleiigs.cpp b/audio/softsynth/appleiigs.cpp new file mode 100644 index 0000000000..80159c79ce --- /dev/null +++ b/audio/softsynth/appleiigs.cpp @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* $URL$ +* $Id$ +* +*/ + +#include "audio/null.h" + +// Plugin interface +// (This can only create a null driver since apple II gs support seeems not to be implemented +// and also is not part of the midi driver architecture. But we need the plugin for the options +// menu in the launcher and for MidiDriver::detectDevice() which is more or less used by all engines.) + +class AppleIIGSMusicPlugin : public NullMusicPlugin { +public: + const char *getName() const { + return _s("Apple II GS Emulator (NOT IMPLEMENTED)"); + } + + const char *getId() const { + return "appleIIgs"; + } + + MusicDevices getDevices() const; +}; + +MusicDevices AppleIIGSMusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_APPLEIIGS)); + return devices; +} + +//#if PLUGIN_ENABLED_DYNAMIC(APPLEIIGS) + //REGISTER_PLUGIN_DYNAMIC(APPLEIIGS, PLUGIN_TYPE_MUSIC, AppleIIGSMusicPlugin); +//#else + REGISTER_PLUGIN_STATIC(APPLEIIGS, PLUGIN_TYPE_MUSIC, AppleIIGSMusicPlugin); +//#endif + diff --git a/audio/softsynth/cms.cpp b/audio/softsynth/cms.cpp new file mode 100644 index 0000000000..fcc15f127e --- /dev/null +++ b/audio/softsynth/cms.cpp @@ -0,0 +1,376 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "audio/softsynth/cms.h" +#include "audio/null.h" + +#include "common/textconsole.h" +#include "common/translation.h" +#include "common/debug.h" + +// CMS/Gameblaster Emulation taken from DosBox + +#define LEFT 0x00 +#define RIGHT 0x01 + +static const byte envelope[8][64] = { + /* zero amplitude */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* maximum amplitude */ + {15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, + 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, + 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, + 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, }, + /* single decay */ + {15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* repetitive decay */ + {15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, + 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, + 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, + 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }, + /* single triangular */ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, + 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* repetitive triangular */ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, + 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, + 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }, + /* single attack */ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* repetitive attack */ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 } +}; + +static const int amplitude_lookup[16] = { + 0*32767/16, 1*32767/16, 2*32767/16, 3*32767/16, + 4*32767/16, 5*32767/16, 6*32767/16, 7*32767/16, + 8*32767/16, 9*32767/16, 10*32767/16, 11*32767/16, + 12*32767/16, 13*32767/16, 14*32767/16, 15*32767/16 +}; + +void CMSEmulator::portWrite(int port, int val) { + switch (port) { + case 0x220: + portWriteIntern(0, 1, val); + break; + + case 0x221: + _saa1099[0].selected_reg = val & 0x1f; + if (_saa1099[0].selected_reg == 0x18 || _saa1099[0].selected_reg == 0x19) { + /* clock the envelope channels */ + if (_saa1099[0].env_clock[0]) + envelope(0, 0); + if (_saa1099[0].env_clock[1]) + envelope(0, 1); + } + break; + + case 0x222: + portWriteIntern(1, 1, val); + break; + + case 0x223: + _saa1099[1].selected_reg = val & 0x1f; + if (_saa1099[1].selected_reg == 0x18 || _saa1099[1].selected_reg == 0x19) { + /* clock the envelope channels */ + if (_saa1099[1].env_clock[0]) + envelope(1, 0); + if (_saa1099[1].env_clock[1]) + envelope(1, 1); + } + break; + + default: + warning("CMSEmulator got port: 0x%X", port); + break; + } +} + +void CMSEmulator::readBuffer(int16 *buffer, const int numSamples) { + update(0, &buffer[0], numSamples); + update(1, &buffer[0], numSamples); +} + +void CMSEmulator::envelope(int chip, int ch) { + SAA1099 *saa = &_saa1099[chip]; + if (saa->env_enable[ch]) { + int step, mode, mask; + mode = saa->env_mode[ch]; + /* step from 0..63 and then loop in steps 32..63 */ + step = saa->env_step[ch] = ((saa->env_step[ch] + 1) & 0x3f) | (saa->env_step[ch] & 0x20); + + mask = 15; + if (saa->env_bits[ch]) + mask &= ~1; /* 3 bit resolution, mask LSB */ + + saa->channels[ch*3+0].envelope[ LEFT] = + saa->channels[ch*3+1].envelope[ LEFT] = + saa->channels[ch*3+2].envelope[ LEFT] = ::envelope[mode][step] & mask; + if (saa->env_reverse_right[ch] & 0x01) { + saa->channels[ch*3+0].envelope[RIGHT] = + saa->channels[ch*3+1].envelope[RIGHT] = + saa->channels[ch*3+2].envelope[RIGHT] = (15 - ::envelope[mode][step]) & mask; + } else { + saa->channels[ch*3+0].envelope[RIGHT] = + saa->channels[ch*3+1].envelope[RIGHT] = + saa->channels[ch*3+2].envelope[RIGHT] = ::envelope[mode][step] & mask; + } + } else { + /* envelope mode off, set all envelope factors to 16 */ + saa->channels[ch*3+0].envelope[ LEFT] = + saa->channels[ch*3+1].envelope[ LEFT] = + saa->channels[ch*3+2].envelope[ LEFT] = + saa->channels[ch*3+0].envelope[RIGHT] = + saa->channels[ch*3+1].envelope[RIGHT] = + saa->channels[ch*3+2].envelope[RIGHT] = 16; + } +} + +void CMSEmulator::update(int chip, int16 *buffer, int length) { + struct SAA1099 *saa = &_saa1099[chip]; + int j, ch; + + /* if the channels are disabled we're done */ + if (!saa->all_ch_enable) { + /* init output data */ + if (chip == 0) { + memset(buffer, 0, sizeof(int16)*length*2); + } + return; + } + + if (chip == 0) { + memset(buffer, 0, sizeof(int16)*length*2); + } + + for (ch = 0; ch < 2; ch++) { + switch (saa->noise_params[ch]) { + case 0: saa->noise[ch].freq = 31250.0 * 2; break; + case 1: saa->noise[ch].freq = 15625.0 * 2; break; + case 2: saa->noise[ch].freq = 7812.5 * 2; break; + case 3: saa->noise[ch].freq = saa->channels[ch * 3].freq; break; + } + } + + /* fill all data needed */ + for (j = 0; j < length; ++j) { + int output_l = 0, output_r = 0; + + /* for each channel */ + for (ch = 0; ch < 6; ch++) { + if (saa->channels[ch].freq == 0.0) + saa->channels[ch].freq = (double)((2 * 15625) << saa->channels[ch].octave) / + (511.0 - (double)saa->channels[ch].frequency); + + /* check the actual position in the square wave */ + saa->channels[ch].counter -= saa->channels[ch].freq; + while (saa->channels[ch].counter < 0) { + /* calculate new frequency now after the half wave is updated */ + saa->channels[ch].freq = (double)((2 * 15625) << saa->channels[ch].octave) / + (511.0 - (double)saa->channels[ch].frequency); + + saa->channels[ch].counter += _sampleRate; + saa->channels[ch].level ^= 1; + + /* eventually clock the envelope counters */ + if (ch == 1 && saa->env_clock[0] == 0) + envelope(chip, 0); + if (ch == 4 && saa->env_clock[1] == 0) + envelope(chip, 1); + } + + /* if the noise is enabled */ + if (saa->channels[ch].noise_enable) { + /* if the noise level is high (noise 0: chan 0-2, noise 1: chan 3-5) */ + if (saa->noise[ch/3].level & 1) { + /* subtract to avoid overflows, also use only half amplitude */ + output_l -= saa->channels[ch].amplitude[ LEFT] * saa->channels[ch].envelope[ LEFT] / 16 / 2; + output_r -= saa->channels[ch].amplitude[RIGHT] * saa->channels[ch].envelope[RIGHT] / 16 / 2; + } + } + + /* if the square wave is enabled */ + if (saa->channels[ch].freq_enable) { + /* if the channel level is high */ + if (saa->channels[ch].level & 1) { + output_l += saa->channels[ch].amplitude[ LEFT] * saa->channels[ch].envelope[ LEFT] / 16; + output_r += saa->channels[ch].amplitude[RIGHT] * saa->channels[ch].envelope[RIGHT] / 16; + } + } + } + + for (ch = 0; ch < 2; ch++) { + /* check the actual position in noise generator */ + saa->noise[ch].counter -= saa->noise[ch].freq; + while (saa->noise[ch].counter < 0) { + saa->noise[ch].counter += _sampleRate; + if (((saa->noise[ch].level & 0x4000) == 0) == ((saa->noise[ch].level & 0x0040) == 0) ) + saa->noise[ch].level = (saa->noise[ch].level << 1) | 1; + else + saa->noise[ch].level <<= 1; + } + } + /* write sound data to the buffer */ + buffer[j*2] += output_l / 6; + buffer[j*2+1] += output_r / 6; + } +} + +void CMSEmulator::portWriteIntern(int chip, int offset, int data) { + SAA1099 *saa = &_saa1099[chip]; + int reg = saa->selected_reg; + int ch; + + switch (reg) { + /* channel i amplitude */ + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + ch = reg & 7; + saa->channels[ch].amplitude[LEFT] = amplitude_lookup[data & 0x0f]; + saa->channels[ch].amplitude[RIGHT] = amplitude_lookup[(data >> 4) & 0x0f]; + break; + + /* channel i frequency */ + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + ch = reg & 7; + saa->channels[ch].frequency = data & 0xff; + break; + + /* channel i octave */ + case 0x10: + case 0x11: + case 0x12: + ch = (reg - 0x10) << 1; + saa->channels[ch + 0].octave = data & 0x07; + saa->channels[ch + 1].octave = (data >> 4) & 0x07; + break; + + /* channel i frequency enable */ + case 0x14: + saa->channels[0].freq_enable = data & 0x01; + saa->channels[1].freq_enable = data & 0x02; + saa->channels[2].freq_enable = data & 0x04; + saa->channels[3].freq_enable = data & 0x08; + saa->channels[4].freq_enable = data & 0x10; + saa->channels[5].freq_enable = data & 0x20; + break; + + /* channel i noise enable */ + case 0x15: + saa->channels[0].noise_enable = data & 0x01; + saa->channels[1].noise_enable = data & 0x02; + saa->channels[2].noise_enable = data & 0x04; + saa->channels[3].noise_enable = data & 0x08; + saa->channels[4].noise_enable = data & 0x10; + saa->channels[5].noise_enable = data & 0x20; + break; + + /* noise generators parameters */ + case 0x16: + saa->noise_params[0] = data & 0x03; + saa->noise_params[1] = (data >> 4) & 0x03; + break; + + /* envelope generators parameters */ + case 0x18: + case 0x19: + ch = reg - 0x18; + saa->env_reverse_right[ch] = data & 0x01; + saa->env_mode[ch] = (data >> 1) & 0x07; + saa->env_bits[ch] = data & 0x10; + saa->env_clock[ch] = data & 0x20; + saa->env_enable[ch] = data & 0x80; + /* reset the envelope */ + saa->env_step[ch] = 0; + break; + + /* channels enable & reset generators */ + case 0x1c: + saa->all_ch_enable = data & 0x01; + saa->sync_state = data & 0x02; + if (data & 0x02) { + int i; + /* Synch & Reset generators */ + for (i = 0; i < 6; i++) { + saa->channels[i].level = 0; + saa->channels[i].counter = 0.0; + } + } + break; + + default: + // The CMS allows all registers to be written, so we just output some debug + // message here + debug(5, "CMS Unknown write to reg %x with %x",reg, data); + } +} + +class CMSMusicPlugin : public NullMusicPlugin { +public: + const char *getName() const { + return _s("Creative Music System Emulator"); + } + + const char *getId() const { + return "cms"; + } + + MusicDevices getDevices() const; +}; + +MusicDevices CMSMusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_CMS)); + return devices; +} + +//#if PLUGIN_ENABLED_DYNAMIC(CMS) + //REGISTER_PLUGIN_DYNAMIC(CMS, PLUGIN_TYPE_MUSIC, CMSMusicPlugin); +//#else + REGISTER_PLUGIN_STATIC(CMS, PLUGIN_TYPE_MUSIC, CMSMusicPlugin); +//#endif diff --git a/audio/softsynth/cms.h b/audio/softsynth/cms.h new file mode 100644 index 0000000000..d5bb7f0a42 --- /dev/null +++ b/audio/softsynth/cms.h @@ -0,0 +1,92 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef SOUND_SOFTSYNTH_CMS_H +#define SOUND_SOFTSYNTH_CMS_H + +#include "common/scummsys.h" + +/* this structure defines a channel */ +struct saa1099_channel { + int frequency; /* frequency (0x00..0xff) */ + int freq_enable; /* frequency enable */ + int noise_enable; /* noise enable */ + int octave; /* octave (0x00..0x07) */ + int amplitude[2]; /* amplitude (0x00..0x0f) */ + int envelope[2]; /* envelope (0x00..0x0f or 0x10 == off) */ + + /* vars to simulate the square wave */ + double counter; + double freq; + int level; +}; + +/* this structure defines a noise channel */ +struct saa1099_noise { + /* vars to simulate the noise generator output */ + double counter; + double freq; + int level; /* noise polynomal shifter */ +}; + +/* this structure defines a SAA1099 chip */ +struct SAA1099 { + int stream; /* our stream */ + int noise_params[2]; /* noise generators parameters */ + int env_enable[2]; /* envelope generators enable */ + int env_reverse_right[2]; /* envelope reversed for right channel */ + int env_mode[2]; /* envelope generators mode */ + int env_bits[2]; /* non zero = 3 bits resolution */ + int env_clock[2]; /* envelope clock mode (non-zero external) */ + int env_step[2]; /* current envelope step */ + int all_ch_enable; /* all channels enable */ + int sync_state; /* sync all channels */ + int selected_reg; /* selected register */ + struct saa1099_channel channels[6]; /* channels */ + struct saa1099_noise noise[2]; /* noise generators */ +}; + +class CMSEmulator { +public: + CMSEmulator(uint32 sampleRate) { + _sampleRate = sampleRate; + memset(_saa1099, 0, sizeof(SAA1099)*2); + } + + ~CMSEmulator() { } + + void portWrite(int port, int val); + void readBuffer(int16 *buffer, const int numSamples); +private: + uint32 _sampleRate; + + SAA1099 _saa1099[2]; + + void envelope(int chip, int ch); + void update(int chip, int16 *buffer, int length); + void portWriteIntern(int chip, int offset, int data); +}; + + +#endif diff --git a/audio/softsynth/emumidi.h b/audio/softsynth/emumidi.h new file mode 100644 index 0000000000..35c81490e4 --- /dev/null +++ b/audio/softsynth/emumidi.h @@ -0,0 +1,116 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef SOUND_SOFTSYNTH_EMUMIDI_H +#define SOUND_SOFTSYNTH_EMUMIDI_H + +#include "audio/audiostream.h" +#include "audio/mididrv.h" +#include "audio/mixer.h" + +#define FIXP_SHIFT 16 + +class MidiDriver_Emulated : public Audio::AudioStream, public MidiDriver { +protected: + bool _isOpen; + Audio::Mixer *_mixer; + Audio::SoundHandle _mixerSoundHandle; + +private: + Common::TimerManager::TimerProc _timerProc; + void *_timerParam; + + int _nextTick; + int _samplesPerTick; + +protected: + virtual void generateSamples(int16 *buf, int len) = 0; + virtual void onTimer() {} + + int _baseFreq; + +public: + MidiDriver_Emulated(Audio::Mixer *mixer) : _mixer(mixer) { + _isOpen = false; + + _timerProc = 0; + _timerParam = 0; + + _nextTick = 0; + _samplesPerTick = 0; + + _baseFreq = 250; + } + + int open() { + _isOpen = true; + + int d = getRate() / _baseFreq; + int r = getRate() % _baseFreq; + + // This is equivalent to (getRate() << FIXP_SHIFT) / BASE_FREQ + // but less prone to arithmetic overflow. + + _samplesPerTick = (d << FIXP_SHIFT) + (r << FIXP_SHIFT) / _baseFreq; + return 0; + } + + void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + _timerProc = timer_proc; + _timerParam = timer_param; + } + + uint32 getBaseTempo() { return 1000000 / _baseFreq; } + + + // AudioStream API + int readBuffer(int16 *data, const int numSamples) { + const int stereoFactor = isStereo() ? 2 : 1; + int len = numSamples / stereoFactor; + int step; + + do { + step = len; + if (step > (_nextTick >> FIXP_SHIFT)) + step = (_nextTick >> FIXP_SHIFT); + + generateSamples(data, step); + + _nextTick -= step << FIXP_SHIFT; + if (!(_nextTick >> FIXP_SHIFT)) { + if (_timerProc) + (*_timerProc)(_timerParam); + onTimer(); + _nextTick += _samplesPerTick; + } + data += step * stereoFactor; + len -= step; + } while (len); + + return numSamples; + } + bool endOfData() const { return false; } +}; + +#endif diff --git a/audio/softsynth/fluidsynth.cpp b/audio/softsynth/fluidsynth.cpp new file mode 100644 index 0000000000..bd016548ec --- /dev/null +++ b/audio/softsynth/fluidsynth.cpp @@ -0,0 +1,254 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "common/scummsys.h" + +#ifdef USE_FLUIDSYNTH + +#include "common/config-manager.h" +#include "audio/musicplugin.h" +#include "audio/mpu401.h" +#include "audio/softsynth/emumidi.h" + +#include + +class MidiDriver_FluidSynth : public MidiDriver_Emulated { +private: + MidiChannel_MPU401 _midiChannels[16]; + fluid_settings_t *_settings; + fluid_synth_t *_synth; + int _soundFont; + int _outputRate; + Audio::SoundHandle _handle; + +protected: + // Because GCC complains about casting from const to non-const... + void setInt(const char *name, int val); + void setNum(const char *name, double num); + void setStr(const char *name, const char *str); + + void generateSamples(int16 *buf, int len); + +public: + MidiDriver_FluidSynth(Audio::Mixer *mixer); + + int open(); + void close(); + void send(uint32 b); + + MidiChannel *allocateChannel(); + MidiChannel *getPercussionChannel(); + + // AudioStream API + bool isStereo() const { return true; } + int getRate() const { return _outputRate; } +}; + +// MidiDriver method implementations + +MidiDriver_FluidSynth::MidiDriver_FluidSynth(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer) { + + for (int i = 0; i < ARRAYSIZE(_midiChannels); i++) { + _midiChannels[i].init(this, i); + } + + // It ought to be possible to get FluidSynth to generate samples at + // lower + + _outputRate = _mixer->getOutputRate(); + if (_outputRate < 22050) + _outputRate = 22050; + else if (_outputRate > 96000) + _outputRate = 96000; +} + +void MidiDriver_FluidSynth::setInt(const char *name, int val) { + char *name2 = strdup(name); + + fluid_settings_setint(_settings, name2, val); + free(name2); +} + +void MidiDriver_FluidSynth::setNum(const char *name, double val) { + char *name2 = strdup(name); + + fluid_settings_setnum(_settings, name2, val); + free(name2); +} + +void MidiDriver_FluidSynth::setStr(const char *name, const char *val) { + char *name2 = strdup(name); + char *val2 = strdup(val); + + fluid_settings_setstr(_settings, name2, val2); + free(name2); + free(val2); +} + +int MidiDriver_FluidSynth::open() { + if (_isOpen) + return MERR_ALREADY_OPEN; + + if (!ConfMan.hasKey("soundfont")) + error("FluidSynth requires a 'soundfont' setting"); + + _settings = new_fluid_settings(); + + // The default gain setting is ridiculously low - at least for me. This + // cannot be fixed by ScummVM's volume settings because they can only + // soften the sound, not amplify it, so instead we add an option to + // adjust the gain of FluidSynth itself. + + double gain = (double)ConfMan.getInt("midi_gain") / 100.0; + + setNum("synth.gain", gain); + setNum("synth.sample-rate", _outputRate); + + _synth = new_fluid_synth(_settings); + + // In theory, this ought to reduce CPU load... but it doesn't make any + // noticeable difference for me, so disable it for now. + + // fluid_synth_set_interp_method(_synth, -1, FLUID_INTERP_LINEAR); + // fluid_synth_set_reverb_on(_synth, 0); + // fluid_synth_set_chorus_on(_synth, 0); + + const char *soundfont = ConfMan.get("soundfont").c_str(); + + _soundFont = fluid_synth_sfload(_synth, soundfont, 1); + if (_soundFont == -1) + error("Failed loading custom sound font '%s'", soundfont); + + MidiDriver_Emulated::open(); + + // The MT-32 emulator uses kSFXSoundType here. I don't know why. + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + return 0; +} + +void MidiDriver_FluidSynth::close() { + if (!_isOpen) + return; + _isOpen = false; + + _mixer->stopHandle(_handle); + + if (_soundFont != -1) + fluid_synth_sfunload(_synth, _soundFont, 1); + + delete_fluid_synth(_synth); + delete_fluid_settings(_settings); +} + +void MidiDriver_FluidSynth::send(uint32 b) { + //byte param3 = (byte) ((b >> 24) & 0xFF); + uint param2 = (byte) ((b >> 16) & 0xFF); + uint param1 = (byte) ((b >> 8) & 0xFF); + byte cmd = (byte) (b & 0xF0); + byte chan = (byte) (b & 0x0F); + + switch (cmd) { + case 0x80: // Note Off + fluid_synth_noteoff(_synth, chan, param1); + break; + case 0x90: // Note On + fluid_synth_noteon(_synth, chan, param1, param2); + break; + case 0xA0: // Aftertouch + break; + case 0xB0: // Control Change + fluid_synth_cc(_synth, chan, param1, param2); + break; + case 0xC0: // Program Change + fluid_synth_program_change(_synth, chan, param1); + break; + case 0xD0: // Channel Pressure + break; + case 0xE0: // Pitch Bend + fluid_synth_pitch_bend(_synth, chan, (param2 << 7) | param1); + break; + case 0xF0: // SysEx + // We should never get here! SysEx information has to be + // sent via high-level semantic methods. + warning("MidiDriver_FluidSynth: Receiving SysEx command on a send() call"); + break; + default: + warning("MidiDriver_FluidSynth: Unknown send() command 0x%02X", cmd); + break; + } +} + +MidiChannel *MidiDriver_FluidSynth::allocateChannel() { + for (int i = 0; i < ARRAYSIZE(_midiChannels); i++) { + if (i != 9 && _midiChannels[i].allocate()) + return &_midiChannels[i]; + } + return NULL; +} + +MidiChannel *MidiDriver_FluidSynth::getPercussionChannel() { + return &_midiChannels[9]; +} + +void MidiDriver_FluidSynth::generateSamples(int16 *data, int len) { + fluid_synth_write_s16(_synth, len, data, 0, 2, data, 1, 2); +} + + +// Plugin interface + +class FluidSynthMusicPlugin : public MusicPluginObject { +public: + const char *getName() const { + return "FluidSynth"; + } + + const char *getId() const { + return "fluidsynth"; + } + + MusicDevices getDevices() const; + Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const; +}; + +MusicDevices FluidSynthMusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_GM)); + return devices; +} + +Common::Error FluidSynthMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const { + *mididriver = new MidiDriver_FluidSynth(g_system->getMixer()); + + return Common::kNoError; +} + +//#if PLUGIN_ENABLED_DYNAMIC(FLUIDSYNTH) + //REGISTER_PLUGIN_DYNAMIC(FLUIDSYNTH, PLUGIN_TYPE_MUSIC, FluidSynthMusicPlugin); +//#else + REGISTER_PLUGIN_STATIC(FLUIDSYNTH, PLUGIN_TYPE_MUSIC, FluidSynthMusicPlugin); +//#endif + +#endif diff --git a/audio/softsynth/fmtowns_pc98/towns_audio.cpp b/audio/softsynth/fmtowns_pc98/towns_audio.cpp new file mode 100644 index 0000000000..e019aa2481 --- /dev/null +++ b/audio/softsynth/fmtowns_pc98/towns_audio.cpp @@ -0,0 +1,1583 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/softsynth/fmtowns_pc98/towns_audio.h" +#include "common/endian.h" +#include "backends/audiocd/audiocd.h" + + +class TownsAudio_PcmChannel { +friend class TownsAudioInterface; +public: + TownsAudio_PcmChannel(); + ~TownsAudio_PcmChannel(); + +private: + void loadExtData(uint8 *buffer, uint32 size); + void setupLoop(uint32 start, uint32 len); + void clear(); + + void envAttack(); + void envDecay(); + void envSustain(); + void envRelease(); + + uint8 *curInstrument; + uint8 note; + uint8 velo; + + int8 *data; + int8 *dataEnd; + + int8 *loopEnd; + uint32 loopLen; + + uint16 stepNote; + uint16 stepPitch; + uint16 step; + + uint8 panLeft; + uint8 panRight; + + uint32 pos; + + uint8 envTotalLevel; + uint8 envAttackRate; + uint8 envDecayRate; + uint8 envSustainLevel; + uint8 envSustainRate; + uint8 envReleaseRate; + + int16 envStep; + int16 envCurrentLevel; + + EnvelopeState envState; + + int8 *extData; +}; + +class TownsAudio_WaveTable { +friend class TownsAudioInterface; +public: + TownsAudio_WaveTable(); + ~TownsAudio_WaveTable(); + +private: + void readHeader(const uint8 *buffer); + void readData(const uint8 *buffer); + void clear(); + + char name[9]; + int32 id; + uint32 size; + uint32 loopStart; + uint32 loopLen; + uint16 rate; + uint16 rateOffs; + uint16 baseNote; + int8 *data; +}; + +TownsAudioInterface::TownsAudioInterface(Audio::Mixer *mixer, TownsAudioInterfacePluginDriver *driver) : TownsPC98_FmSynth(mixer, kTypeTowns), + _fmInstruments(0), _pcmInstruments(0), _pcmChan(0), _waveTables(0), _waveTablesTotalDataSize(0), + _baserate(55125.0f / (float)mixer->getOutputRate()), _tickLength(0), _timer(0), _drv(driver), + _pcmSfxChanMask(0), _musicVolume(Audio::Mixer::kMaxMixerVolume), _sfxVolume(Audio::Mixer::kMaxMixerVolume), + _outputVolumeFlags(0), _outputMuteFlags(0), _pcmChanOut(0), _pcmChanReserved(0), _pcmChanKeyPressed(0), + _pcmChanEffectPlaying(0), _pcmChanKeyPlaying(0), _ready(false) { + +#define INTCB(x) &TownsAudioInterface::intf_##x + static const TownsAudioIntfCallback intfCb[] = { + // 0 + INTCB(reset), + INTCB(keyOn), + INTCB(keyOff), + INTCB(setPanPos), + // 4 + INTCB(setInstrument), + INTCB(loadInstrument), + INTCB(notImpl), + INTCB(setPitch), + // 8 + INTCB(setLevel), + INTCB(chanOff), + INTCB(notImpl), + INTCB(notImpl), + // 12 + INTCB(notImpl), + INTCB(notImpl), + INTCB(notImpl), + INTCB(notImpl), + // 16 + INTCB(notImpl), + INTCB(writeReg), + INTCB(notImpl), + INTCB(writeRegBuffer), + // 20 + INTCB(readRegBuffer), + INTCB(setTimerA), + INTCB(setTimerB), + INTCB(enableTimerA), + // 24 + INTCB(enableTimerB), + INTCB(notImpl), + INTCB(notImpl), + INTCB(notImpl), + // 28 + INTCB(notImpl), + INTCB(notImpl), + INTCB(notImpl), + INTCB(notImpl), + // 32 + INTCB(loadSamples), + INTCB(reserveEffectChannels), + INTCB(loadWaveTable), + INTCB(unloadWaveTable), + // 36 + INTCB(notImpl), + INTCB(pcmPlayEffect), + INTCB(notImpl), + INTCB(pcmChanOff), + // 40 + INTCB(pcmEffectPlaying), + INTCB(notImpl), + INTCB(notImpl), + INTCB(notImpl), + // 44 + INTCB(notImpl), + INTCB(notImpl), + INTCB(notImpl), + INTCB(notImpl), + // 48 + INTCB(notImpl), + INTCB(notImpl), + INTCB(fmKeyOn), + INTCB(fmKeyOff), + // 52 + INTCB(fmSetPanPos), + INTCB(fmSetInstrument), + INTCB(fmLoadInstrument), + INTCB(notImpl), + // 56 + INTCB(fmSetPitch), + INTCB(fmSetLevel), + INTCB(fmReset), + INTCB(notImpl), + // 60 + INTCB(notImpl), + INTCB(notImpl), + INTCB(notImpl), + INTCB(notImpl), + // 64 + INTCB(notImpl), + INTCB(notImpl), + INTCB(notImpl), + INTCB(setOutputVolume), + // 68 + INTCB(resetOutputVolume), + INTCB(notImpl), + INTCB(updateOutputVolume), + INTCB(notImpl), + // 72 + INTCB(notImpl), + INTCB(cdaToggle), + INTCB(notImpl), + INTCB(notImpl), + // 76 + INTCB(notImpl), + INTCB(notImpl), + INTCB(notImpl), + INTCB(notImpl), + // 80 + INTCB(pcmUpdateEnvelopeGenerator), + INTCB(notImpl) + }; +#undef INTCB + + _intfOpcodes = intfCb; + + memset(_fmSaveReg, 0, sizeof(_fmSaveReg)); + memset(_outputLevel, 0, sizeof(_outputLevel)); + + _timerBase = (uint32)(_baserate * 1000000.0f); + _tickLength = 2 * _timerBase; +} + +TownsAudioInterface::~TownsAudioInterface() { + _ready = false; + deinit(); + + delete[] _fmSaveReg[0]; + delete[] _fmSaveReg[1]; + delete[] _fmInstruments; + delete[] _pcmInstruments; + delete[] _waveTables; + delete[] _pcmChan; +} + +bool TownsAudioInterface::init() { + if (_ready) + return true; + + if (!TownsPC98_FmSynth::init()) + return false; + + _fmSaveReg[0] = new uint8[256]; + _fmSaveReg[1] = new uint8[256]; + _fmInstruments = new uint8[128 * 48]; + _pcmInstruments = new uint8[32 * 128]; + _waveTables = new TownsAudio_WaveTable[128]; + _pcmChan = new TownsAudio_PcmChannel[8]; + + _timer = 0; + + setVolumeChannelMasks(-1, 0); + + _ready = true; + callback(0); + + return true; +} + +int TownsAudioInterface::callback(int command, ...) { + if (!_ready) + return 1; + + va_list args; + va_start(args, command); + + if (command > 81) { + va_end(args); + return 4; + } + + int res = (this->*_intfOpcodes[command])(args); + + va_end(args); + return res; +} + +void TownsAudioInterface::setMusicVolume(int volume) { + _musicVolume = CLIP(volume, 0, Audio::Mixer::kMaxMixerVolume); + setVolumeIntern(_musicVolume, _sfxVolume); +} + +void TownsAudioInterface::setSoundEffectVolume(int volume) { + _sfxVolume = CLIP(volume, 0, Audio::Mixer::kMaxMixerVolume); + setVolumeIntern(_musicVolume, _sfxVolume); +} + +void TownsAudioInterface::setSoundEffectChanMask(int mask) { + _pcmSfxChanMask = mask >> 6; + mask &= 0x3f; + setVolumeChannelMasks(~mask, mask); +} + +void TownsAudioInterface::nextTickEx(int32 *buffer, uint32 bufferSize) { + if (!_ready) + return; + + for (uint32 i = 0; i < bufferSize; i++) { + _timer += _tickLength; + while (_timer > 0x514767) { + _timer -= 0x514767; + + for (int ii = 0; ii < 8; ii++) { + if ((_pcmChanKeyPlaying & _chanFlags[ii]) || (_pcmChanEffectPlaying & _chanFlags[ii])) { + TownsAudio_PcmChannel *s = &_pcmChan[ii]; + s->pos += s->step; + + if (&s->data[s->pos >> 11] >= s->loopEnd) { + if (s->loopLen) { + s->pos -= s->loopLen; + } else { + s->pos = 0; + _pcmChanEffectPlaying &= ~_chanFlags[ii]; + _pcmChanKeyPlaying &= ~_chanFlags[ii]; + } + } + } + } + } + + int32 finOutL = 0; + int32 finOutR = 0; + + for (int ii = 0; ii < 8; ii++) { + if (_pcmChanOut & _chanFlags[ii]) { + int32 o = _pcmChan[ii].data[_pcmChan[ii].pos >> 11] * _pcmChan[ii].velo; + if ((1 << ii) & (~_pcmSfxChanMask)) + o = (o * _musicVolume) / Audio::Mixer::kMaxMixerVolume; + if ((1 << ii) & _pcmSfxChanMask) + o = (o * _sfxVolume) / Audio::Mixer::kMaxMixerVolume; + if (_pcmChan[ii].panLeft) + finOutL += ((o * _pcmChan[ii].panLeft) >> 3); + if (_pcmChan[ii].panRight) + finOutR += ((o * _pcmChan[ii].panRight) >> 3); + if (!((_pcmChanKeyPlaying & _chanFlags[ii]) || (_pcmChanEffectPlaying & _chanFlags[ii]))) + _pcmChanOut &= ~_chanFlags[ii]; + } + } + + buffer[i << 1] += finOutL; + buffer[(i << 1) + 1] += finOutR; + } +} + +void TownsAudioInterface::timerCallbackA() { + Common::StackLock lock(_mutex); + if (_drv && _ready) + _drv->timerCallback(0); +} + +void TownsAudioInterface::timerCallbackB() { + Common::StackLock lock(_mutex); + if (_ready) { + if (_drv) + _drv->timerCallback(1); + callback(80); + } +} + +int TownsAudioInterface::intf_reset(va_list &args) { + fmReset(); + pcmReset(); + callback(68); + return 0; +} + +int TownsAudioInterface::intf_keyOn(va_list &args) { + int chan = va_arg(args, int); + int note = va_arg(args, int); + int velo = va_arg(args, int); + return (chan & 0x40) ? pcmKeyOn(chan, note, velo) : fmKeyOn(chan, note, velo); +} + +int TownsAudioInterface::intf_keyOff(va_list &args) { + int chan = va_arg(args, int); + return (chan & 0x40) ? pcmKeyOff(chan) : fmKeyOff(chan); +} + +int TownsAudioInterface::intf_setPanPos(va_list &args) { + int chan = va_arg(args, int); + int mode = va_arg(args, int); + return (chan & 0x40) ? pcmSetPanPos(chan, mode) : fmSetPanPos(chan, mode); +} + +int TownsAudioInterface::intf_setInstrument(va_list &args) { + int chan = va_arg(args, int); + int instrId = va_arg(args, int); + return (chan & 0x40) ? pcmSetInstrument(chan, instrId) : fmSetInstrument(chan, instrId); +} + +int TownsAudioInterface::intf_loadInstrument(va_list &args) { + int chanType = va_arg(args, int); + int instrId = va_arg(args, int); + uint8 *instrData = va_arg(args, uint8 *); + return (chanType & 0x40) ? pcmLoadInstrument(instrId, instrData) : fmLoadInstrument(instrId, instrData); +} + +int TownsAudioInterface::intf_setPitch(va_list &args) { + int chan = va_arg(args, int); + int16 pitch = (int16)(va_arg(args, int) & 0xffff); + return (chan & 0x40) ? pcmSetPitch(chan, pitch) : fmSetPitch(chan, pitch); +} + +int TownsAudioInterface::intf_setLevel(va_list &args) { + int chan = va_arg(args, int); + int lvl = va_arg(args, int); + return (chan & 0x40) ? pcmSetLevel(chan, lvl) : fmSetLevel(chan, lvl); +} + +int TownsAudioInterface::intf_chanOff(va_list &args) { + int chan = va_arg(args, int); + return (chan & 0x40) ? pcmChanOff(chan) : fmChanOff(chan); +} + +int TownsAudioInterface::intf_writeReg(va_list &args) { + int part = va_arg(args, int) ? 1 : 0; + int reg = va_arg(args, int); + int val = va_arg(args, int); + if ((!part && reg < 0x20) || (part && reg < 0x30) || (reg > 0xb6)) + return 3; + + bufferedWriteReg(part, reg, val); + return 0; +} + +int TownsAudioInterface::intf_writeRegBuffer(va_list &args) { + int part = va_arg(args, int) ? 1 : 0; + int reg = va_arg(args, int); + int val = va_arg(args, int); + + if ((!part && reg < 0x20) || (part && reg < 0x30) || (reg > 0xef)) + return 3; + + _fmSaveReg[part][reg] = val; + return 0; +} + +int TownsAudioInterface::intf_readRegBuffer(va_list &args) { + int part = va_arg(args, int) ? 1 : 0; + int reg = va_arg(args, int); + uint8 *dst = va_arg(args, uint8 *); + *dst = 0; + + if ((!part && reg < 0x20) || (part && reg < 0x30) || (reg > 0xef)) + return 3; + + *dst = _fmSaveReg[part][reg]; + return 0; +} + +int TownsAudioInterface::intf_setTimerA(va_list &args) { + int enable = va_arg(args, int); + int tempo = va_arg(args, int); + + if (enable) { + bufferedWriteReg(0, 0x25, tempo & 3); + bufferedWriteReg(0, 0x24, (tempo >> 2) & 0xff); + bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x05); + } else { + bufferedWriteReg(0, 0x27, (_fmSaveReg[0][0x27] & 0xfa) | 0x10); + } + + return 0; +} + +int TownsAudioInterface::intf_setTimerB(va_list &args) { + int enable = va_arg(args, int); + int tempo = va_arg(args, int); + + if (enable) { + bufferedWriteReg(0, 0x26, tempo & 0xff); + bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x0A); + } else { + bufferedWriteReg(0, 0x27, (_fmSaveReg[0][0x27] & 0xf5) | 0x20); + } + + return 0; +} + +int TownsAudioInterface::intf_enableTimerA(va_list &args) { + bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x15); + return 0; +} + +int TownsAudioInterface::intf_enableTimerB(va_list &args) { + bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x2a); + return 0; +} + +int TownsAudioInterface::intf_loadSamples(va_list &args) { + uint32 dest = va_arg(args, uint32); + int size = va_arg(args, int); + uint8 *src = va_arg(args, uint8*); + + if (dest >= 65536 || size == 0 || size > 65536) + return 3; + if (size + dest > 65536) + return 5; + + int dwIndex = _numWaveTables - 1; + for (uint32 t = _waveTablesTotalDataSize; dwIndex && (dest < t); dwIndex--) + t -= _waveTables[dwIndex].size; + + TownsAudio_WaveTable *s = &_waveTables[dwIndex]; + _waveTablesTotalDataSize -= s->size; + s->size = size; + s->readData(src); + _waveTablesTotalDataSize += s->size; + + return 0; +} + +int TownsAudioInterface::intf_reserveEffectChannels(va_list &args) { + int numChan = va_arg(args, int); + if (numChan > 8) + return 3; + if ((numChan << 13) + _waveTablesTotalDataSize > 65536) + return 5; + + if (numChan == _numReservedChannels) + return 0; + + if (numChan < _numReservedChannels) { + int c = 8 - _numReservedChannels; + for (int i = numChan; i; i--) { + uint8 f = ~_chanFlags[c--]; + _pcmChanEffectPlaying &= f; + } + } else { + int c = 7 - _numReservedChannels; + for (int i = numChan - _numReservedChannels; i; i--) { + uint8 f = ~_chanFlags[c--]; + _pcmChanKeyPressed &= f; + _pcmChanKeyPlaying &= f; + } + } + + static const uint8 reserveChanFlags[] = { 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF }; + _numReservedChannels = numChan; + _pcmChanReserved = reserveChanFlags[_numReservedChannels]; + + return 0; +} + +int TownsAudioInterface::intf_loadWaveTable(va_list &args) { + uint8 *data = va_arg(args, uint8 *); + if (_numWaveTables > 127) + return 3; + + TownsAudio_WaveTable w; + w.readHeader(data); + if (!w.size) + return 6; + + if (_waveTablesTotalDataSize + w.size > 65504) + return 5; + + for (int i = 0; i < _numWaveTables; i++) { + if (_waveTables[i].id == w.id) + return 10; + } + + TownsAudio_WaveTable *s = &_waveTables[_numWaveTables++]; + s->readHeader(data); + + _waveTablesTotalDataSize += s->size; + callback(32, _waveTablesTotalDataSize, s->size, data + 32); + + return 0; +} + +int TownsAudioInterface::intf_unloadWaveTable(va_list &args) { + int id = va_arg(args, int); + + if (id == -1) { + for (int i = 0; i < 128; i++) + _waveTables[i].clear(); + _numWaveTables = 0; + _waveTablesTotalDataSize = 0; + } else { + if (_waveTables) { + for (int i = 0; i < _numWaveTables; i++) { + if (_waveTables[i].id == id) { + _numWaveTables--; + _waveTablesTotalDataSize -= _waveTables[i].size; + _waveTables[i].clear(); + for (; i < _numWaveTables; i++) + memcpy(&_waveTables[i], &_waveTables[i + 1], sizeof(TownsAudio_WaveTable)); + return 0; + } + return 9; + } + } + } + + return 0; +} + +int TownsAudioInterface::intf_pcmPlayEffect(va_list &args) { + int chan = va_arg(args, int); + int note = va_arg(args, int); + int velo = va_arg(args, int); + uint8 *data = va_arg(args, uint8 *); + + if (chan < 0x40 || chan > 0x47) + return 1; + + if (note & 0x80 || velo & 0x80) + return 3; + + chan -= 0x40; + + if (!(_pcmChanReserved & _chanFlags[chan])) + return 7; + + if ((_pcmChanEffectPlaying & _chanFlags[chan])) + return 2; + + TownsAudio_WaveTable w; + w.readHeader(data); + + if (w.size < (w.loopStart + w.loopLen)) + return 13; + + if (!w.size) + return 6; + + TownsAudio_PcmChannel *p = &_pcmChan[chan]; + + _pcmChanNote[chan] = note; + _pcmChanVelo[chan] = velo; + + p->note = note; + p->velo = velo << 1; + + p->loadExtData(data + 32, w.size); + p->setupLoop(w.loopStart, w.loopLen); + + pcmCalcPhaseStep(p, &w); + if (p->step > 2048) + p->step = 2048; + + _pcmChanEffectPlaying |= _chanFlags[chan]; + _pcmChanOut |= _chanFlags[chan]; + + return 0; +} + +int TownsAudioInterface::intf_pcmChanOff(va_list &args) { + int chan = va_arg(args, int); + pcmChanOff(chan); + return 0; +} + +int TownsAudioInterface::intf_pcmEffectPlaying(va_list &args) { + int chan = va_arg(args, int); + if (chan < 0x40 || chan > 0x47) + return 1; + chan -= 0x40; + return (_pcmChanEffectPlaying & _chanFlags[chan]) ? 1 : 0; +} + +int TownsAudioInterface::intf_fmKeyOn(va_list &args) { + int chan = va_arg(args, int); + int note = va_arg(args, int); + int velo = va_arg(args, int); + return fmKeyOn(chan, note, velo); +} + +int TownsAudioInterface::intf_fmKeyOff(va_list &args) { + int chan = va_arg(args, int); + return fmKeyOff(chan); +} + +int TownsAudioInterface::intf_fmSetPanPos(va_list &args) { + int chan = va_arg(args, int); + int mode = va_arg(args, int); + return fmSetPanPos(chan, mode); +} + +int TownsAudioInterface::intf_fmSetInstrument(va_list &args) { + int chan = va_arg(args, int); + int instrId = va_arg(args, int); + return fmSetInstrument(chan, instrId); +} + +int TownsAudioInterface::intf_fmLoadInstrument(va_list &args) { + int instrId = va_arg(args, int); + uint8 *instrData = va_arg(args, uint8 *); + return fmLoadInstrument(instrId, instrData); +} + +int TownsAudioInterface::intf_fmSetPitch(va_list &args) { + int chan = va_arg(args, int); + uint16 freq = va_arg(args, int) & 0xffff; + return fmSetPitch(chan, freq); +} + +int TownsAudioInterface::intf_fmSetLevel(va_list &args) { + int chan = va_arg(args, int); + int lvl = va_arg(args, int); + return fmSetLevel(chan, lvl); +} + +int TownsAudioInterface::intf_fmReset(va_list &args) { + fmReset(); + return 0; +} + +int TownsAudioInterface::intf_setOutputVolume(va_list &args) { + int chanType = va_arg(args, int); + int left = va_arg(args, int); + int right = va_arg(args, int); + + if (left & 0xff80 || right & 0xff80) + return 3; + + static const uint8 flags[] = { 0x0C, 0x30, 0x40, 0x80 }; + + uint8 chan = (chanType & 0x40) ? 8 : 12; + + chanType &= 3; + left = (left & 0x7e) >> 1; + right = (right & 0x7e) >> 1; + + if (chan) + _outputVolumeFlags |= flags[chanType]; + else + _outputVolumeFlags &= ~flags[chanType]; + + if (chanType > 1) { + _outputLevel[chan + chanType] = left; + } else { + if (chanType == 0) + chan -= 8; + _outputLevel[chan] = left; + _outputLevel[chan + 1] = right; + } + + updateOutputVolume(); + + return 0; +} + +int TownsAudioInterface::intf_resetOutputVolume(va_list &args) { + memset(_outputLevel, 0, sizeof(_outputLevel)); + _outputMuteFlags = 0; + _outputVolumeFlags = 0; + updateOutputVolume(); + return 0; +} + +int TownsAudioInterface::intf_updateOutputVolume(va_list &args) { + int flags = va_arg(args, int); + _outputMuteFlags = flags & 3; + updateOutputVolume(); + return 0; +} + +int TownsAudioInterface::intf_cdaToggle(va_list &args) { + //int mode = va_arg(args, int); + //_unkMask = mode ? 0x7f : 0x3f; + return 0; +} + +int TownsAudioInterface::intf_pcmUpdateEnvelopeGenerator(va_list &args) { + for (int i = 0; i < 8; i++) + pcmUpdateEnvelopeGenerator(i); + return 0; +} + +int TownsAudioInterface::intf_notImpl(va_list &args) { + return 4; +} + +void TownsAudioInterface::fmReset() { + TownsPC98_FmSynth::reset(); + + _fmChanPlaying = 0; + memset(_fmChanNote, 0, sizeof(_fmChanNote)); + memset(_fmChanPitch, 0, sizeof(_fmChanPitch)); + + memset(_fmSaveReg[0], 0, 240); + memset(&_fmSaveReg[0][240], 0x7f, 16); + memset(_fmSaveReg[1], 0, 256); + memset(&_fmSaveReg[1][240], 0x7f, 16); + _fmSaveReg[0][243] = _fmSaveReg[0][247] = _fmSaveReg[0][251] = _fmSaveReg[0][255] = _fmSaveReg[1][243] = _fmSaveReg[1][247] = _fmSaveReg[1][251] = _fmSaveReg[1][255] = 0xff; + + for (int i = 0; i < 128; i++) + fmLoadInstrument(i, _fmDefaultInstrument); + + bufferedWriteReg(0, 0x21, 0); + bufferedWriteReg(0, 0x2C, 0x80); + bufferedWriteReg(0, 0x2B, 0); + bufferedWriteReg(0, 0x27, 0x30); + + for (int i = 0; i < 6; i++) { + fmKeyOff(i); + fmSetInstrument(i, 0); + fmSetLevel(i, 127); + } +} + +int TownsAudioInterface::fmKeyOn(int chan, int note, int velo) { + if (chan > 5) + return 1; + if (note < 12 || note > 107 || (velo & 0x80)) + return 3; + if (_fmChanPlaying & _chanFlags[chan]) + return 2; + + _fmChanPlaying |= _chanFlags[chan]; + note -= 12; + + _fmChanNote[chan] = note; + int16 pitch = _fmChanPitch[chan]; + + uint8 part = chan > 2 ? 1 : 0; + if (chan > 2) + chan -= 3; + + int frq = 0; + uint8 bl = 0; + + if (note) { + frq = _frequency[(note - 1) % 12]; + bl = (note - 1) / 12; + } else { + frq = 616; + } + + frq += pitch; + + if (frq < 616) { + if (!bl) { + frq = 616; + } else { + frq += 616; + --bl; + } + } else if (frq > 1232) { + if (bl == 7) { + frq = 15500; + } else { + frq -= 616; + ++bl; + } + } + + frq |= (bl << 11); + + bufferedWriteReg(part, chan + 0xa4, (frq >> 8) & 0xff); + bufferedWriteReg(part, chan + 0xa0, frq & 0xff); + + velo = (velo >> 2) + 96; + uint16 c = _carrier[_fmSaveReg[part][0xb0 + chan] & 7]; + _fmSaveReg[part][0xe0 + chan] = velo; + + for (uint8 reg = 0x40 + chan; reg < 0x50; reg += 4) { + c += c; + if (c & 0x100) { + c &= 0xff; + bufferedWriteReg(part, reg, (((((((_fmSaveReg[part][0x80 + reg] ^ 0x7f) * velo) >> 7) + 1) * _fmSaveReg[part][0xd0 + chan]) >> 7) + 1) ^ 0x7f); + } + } + + uint8 v = chan; + if (part) + v |= 4; + + for (uint8 reg = 0x80 + chan; reg < 0x90; reg += 4) + writeReg(part, reg, _fmSaveReg[part][reg] | 0x0f); + + writeReg(0, 0x28, v); + + for (uint8 reg = 0x80 + chan; reg < 0x90; reg += 4) + writeReg(part, reg, _fmSaveReg[part][reg]); + + bufferedWriteReg(0, 0x28, v | 0xf0); + + return 0; +} + +int TownsAudioInterface::fmKeyOff(int chan) { + if (chan > 5) + return 1; + _fmChanPlaying &= ~_chanFlags[chan]; + if (chan > 2) + chan++; + bufferedWriteReg(0, 0x28, chan); + return 0; +} + +int TownsAudioInterface::fmChanOff(int chan) { + if (chan > 5) + return 1; + _fmChanPlaying &= ~_chanFlags[chan]; + + uint8 part = chan > 2 ? 1 : 0; + if (chan > 2) + chan -= 3; + + for (uint8 reg = 0x80 + chan; reg < 0x90; reg += 4) + writeReg(part, reg, _fmSaveReg[part][reg] | 0x0f); + + if (part) + chan += 4; + writeReg(0, 0x28, chan); + return 0; +} + +int TownsAudioInterface::fmSetPanPos(int chan, int value) { + if (chan > 5) + return 1; + + uint8 part = chan > 2 ? 1 : 0; + if (chan > 2) + chan -= 3; + + if (value > 0x40) + value = 0x40; + else if (value < 0x40) + value = 0x80; + else + value = 0xC0; + + bufferedWriteReg(part, 0xb4 + chan, (_fmSaveReg[part][0xb4 + chan] & 0x3f) | value); + return 0; +} + +int TownsAudioInterface::fmSetInstrument(int chan, int instrId) { + if (chan > 5) + return 1; + if (instrId > 127) + return 3; + + uint8 part = chan > 2 ? 1 : 0; + if (chan > 2) + chan -= 3; + + uint8 *src = &_fmInstruments[instrId * 48 + 8]; + + uint16 c = _carrier[src[24] & 7]; + uint8 reg = 0x30 + chan; + + for (; reg < 0x40; reg += 4) + bufferedWriteReg(part, reg, *src++); + + for (; reg < 0x50; reg += 4) { + uint8 v = *src++; + _fmSaveReg[part][0x80 + reg] = _fmSaveReg[part][reg] = v; + c += c; + if (c & 0x100) { + c &= 0xff; + v = 127; + } + writeReg(part, reg, v); + } + + for (; reg < 0x90; reg += 4) + bufferedWriteReg(part, reg, *src++); + + reg += 0x20; + bufferedWriteReg(part, reg, *src++); + + uint8 v = *src++; + reg += 4; + if (v < 64) + v |= (_fmSaveReg[part][reg] & 0xc0); + bufferedWriteReg(part, reg, v); + + return 0; +} + +int TownsAudioInterface::fmLoadInstrument(int instrId, const uint8 *data) { + if (instrId > 127) + return 3; + assert(data); + memcpy(&_fmInstruments[instrId * 48], data, 48); + return 0; +} + +int TownsAudioInterface::fmSetPitch(int chan, int pitch) { + if (chan > 5) + return 1; + + uint8 bl = _fmChanNote[chan]; + int frq = 0; + + if (pitch < 0) { + if (bl) { + if (pitch < -8008) + pitch = -8008; + pitch *= -1; + pitch /= 13; + frq = _frequency[(bl - 1) % 12] - pitch; + bl = (bl - 1) / 12; + _fmChanPitch[chan] = -pitch; + + if (frq < 616) { + if (bl) { + frq += 616; + bl--; + } else { + frq = 616; + bl = 0; + } + } + } else { + frq = 616; + bl = 0; + } + + } else if (pitch > 0) { + if (bl < 96) { + if (pitch > 8008) + pitch = 8008; + pitch /= 13; + + if (bl) { + frq = _frequency[(bl - 1) % 12] + pitch; + bl = (bl - 1) / 12; + } else { + frq = 616; + bl = 0; + } + + _fmChanPitch[chan] = pitch; + + if (frq > 1232) { + if (bl < 7) { + frq -= 616; + bl++; + } else { + frq = 1164; + bl = 7; + } + } else { + if (bl >= 7 && frq > 1164) + frq = 1164; + } + + } else { + frq = 1164; + bl = 7; + } + } else { + _fmChanPitch[chan] = 0; + if (bl) { + frq = _frequency[(bl - 1) % 12]; + bl = (bl - 1) / 12; + } else { + frq = 616; + bl = 0; + } + } + + uint8 part = chan > 2 ? 1 : 0; + if (chan > 2) + chan -= 3; + + frq |= (bl << 11); + + bufferedWriteReg(part, chan + 0xa4, (frq >> 8)); + bufferedWriteReg(part, chan + 0xa0, (frq & 0xff)); + + return 0; +} + +int TownsAudioInterface::fmSetLevel(int chan, int lvl) { + if (chan > 5) + return 1; + if (lvl > 127) + return 3; + + uint8 part = chan > 2 ? 1 : 0; + if (chan > 2) + chan -= 3; + + uint16 c = _carrier[_fmSaveReg[part][0xb0 + chan] & 7]; + _fmSaveReg[part][0xd0 + chan] = lvl; + + for (uint8 reg = 0x40 + chan; reg < 0x50; reg += 4) { + c += c; + if (c & 0x100) { + c &= 0xff; + bufferedWriteReg(part, reg, (((((((_fmSaveReg[part][0x80 + reg] ^ 0x7f) * lvl) >> 7) + 1) * _fmSaveReg[part][0xe0 + chan]) >> 7) + 1) ^ 0x7f); + } + } + return 0; +} + +void TownsAudioInterface::bufferedWriteReg(uint8 part, uint8 regAddress, uint8 value) { + _fmSaveReg[part][regAddress] = value; + writeReg(part, regAddress, value); +} + +void TownsAudioInterface::pcmReset() { + _pcmChanOut = 0; + _pcmChanReserved = _pcmChanKeyPressed = _pcmChanEffectPlaying = _pcmChanKeyPlaying = 0; + _numReservedChannels = 0; + + memset(_pcmChanNote, 0, 8); + memset(_pcmChanVelo, 0, 8); + memset(_pcmChanLevel, 0, 8); + + for (int i = 0; i < 8; i++) + _pcmChan[i].clear(); + + memset(_pcmInstruments, 0, 128 * 32); + static uint8 name[] = { 0x4E, 0x6F, 0x20, 0x44, 0x61, 0x74, 0x61, 0x21 }; + for (int i = 0; i < 32; i++) + memcpy(_pcmInstruments + i * 128, name, 8); + + for (int i = 0; i < 128; i++) + _waveTables[i].clear(); + _numWaveTables = 0; + _waveTablesTotalDataSize = 0; + + for (int i = 0x40; i < 0x48; i++) { + pcmSetInstrument(i, 0); + pcmSetLevel(i, 127); + } +} + +int TownsAudioInterface::pcmKeyOn(int chan, int note, int velo) { + if (chan < 0x40 || chan > 0x47) + return 1; + + if (note & 0x80 || velo & 0x80) + return 3; + + chan -= 0x40; + + if ((_pcmChanReserved & _chanFlags[chan]) || (_pcmChanKeyPressed & _chanFlags[chan])) + return 2; + + _pcmChanNote[chan] = note; + _pcmChanVelo[chan] = velo; + + TownsAudio_PcmChannel *p = &_pcmChan[chan]; + p->note = note; + + uint8 *instr = _pcmChan[chan].curInstrument; + int i = 0; + for (; i < 8; i++) { + if (note <= instr[16 + 2 * i]) + break; + } + + if (i == 8) + return 8; + + int il = i << 3; + p->note += instr[il + 70]; + + p->envTotalLevel = instr[il + 64]; + p->envAttackRate = instr[il + 65]; + p->envDecayRate = instr[il + 66]; + p->envSustainLevel = instr[il + 67]; + p->envSustainRate = instr[il + 68]; + p->envReleaseRate = instr[il + 69]; + p->envStep = 0; + + int32 id = (int32)READ_LE_UINT32(&instr[i * 4 + 32]); + + for (i = 0; i < _numWaveTables; i++) { + if (id == _waveTables[i].id) + break; + } + + if (i == _numWaveTables) + return 9; + + TownsAudio_WaveTable *w = &_waveTables[i]; + + p->data = w->data; + p->dataEnd = w->data + w->size; + p->setupLoop(w->loopStart, w->loopLen); + + pcmCalcPhaseStep(p, w); + + uint32 lvl = _pcmChanLevel[chan] * _pcmChanVelo[chan]; + p->envTotalLevel = ((p->envTotalLevel * lvl) >> 14) & 0xff; + p->envSustainLevel = ((p->envSustainLevel * lvl) >> 14) & 0xff; + + p->envAttack(); + p->velo = (p->envCurrentLevel >> 8) << 1; + + _pcmChanKeyPressed |= _chanFlags[chan]; + _pcmChanKeyPlaying |= _chanFlags[chan]; + _pcmChanOut |= _chanFlags[chan]; + + return 0; +} + +int TownsAudioInterface::pcmKeyOff(int chan) { + if (chan < 0x40 || chan > 0x47) + return 1; + + chan -= 0x40; + _pcmChanKeyPressed &= ~_chanFlags[chan]; + _pcmChan[chan].envRelease(); + return 0; +} + +int TownsAudioInterface::pcmChanOff(int chan) { + if (chan < 0x40 || chan > 0x47) + return 1; + + chan -= 0x40; + + _pcmChanKeyPressed &= ~_chanFlags[chan]; + _pcmChanEffectPlaying &= ~_chanFlags[chan]; + _pcmChanKeyPlaying &= ~_chanFlags[chan]; + _pcmChanOut &= ~_chanFlags[chan]; + + return 0; +} + +int TownsAudioInterface::pcmSetPanPos(int chan, int mode) { + if (chan > 0x47) + return 1; + if (mode & 0x80) + return 3; + + chan -= 0x40; + uint8 blc = 0x77; + + if (mode > 64) { + mode -= 64; + blc = ((blc ^ (mode >> 3)) + (mode << 4)) & 0xff; + } else if (mode < 64) { + mode = (mode >> 3) ^ 7; + blc = ((119 + mode) ^ (mode << 4)) & 0xff; + } + + _pcmChan[chan].panLeft = blc & 0x0f; + _pcmChan[chan].panRight = blc >> 4; + + return 0; +} + +int TownsAudioInterface::pcmSetInstrument(int chan, int instrId) { + if (chan > 0x47) + return 1; + if (instrId > 31) + return 3; + chan -= 0x40; + _pcmChan[chan].curInstrument = &_pcmInstruments[instrId * 128]; + return 0; +} + +int TownsAudioInterface::pcmLoadInstrument(int instrId, const uint8 *data) { + if (instrId > 31) + return 3; + assert(data); + memcpy(&_pcmInstruments[instrId * 128], data, 128); + return 0; +} + +int TownsAudioInterface::pcmSetPitch(int chan, int pitch) { + if (chan > 0x47) + return 1; + + if (pitch < -8192 || pitch > 8191) + return 3; + + chan -= 0x40; + TownsAudio_PcmChannel *p = &_pcmChan[chan]; + + uint32 pts = 0x4000; + + if (pitch < 0) + pts = (0x20000000 / (-pitch + 0x2001)) >> 2; + else if (pitch > 0) + pts = (((pitch + 0x2001) << 16) / 0x2000) >> 2; + + p->stepPitch = pts & 0xffff; + p->step = (p->stepNote * p->stepPitch) >> 14; + +// if (_pcmChanUnkFlag & _chanFlags[chan]) +// unk[chan] = (((p->step * 1000) << 11) / 98) / 20833; + + /*else*/ + if ((_pcmChanEffectPlaying & _chanFlags[chan]) && (p->step > 2048)) + p->step = 2048; + + return 0; +} + +int TownsAudioInterface::pcmSetLevel(int chan, int lvl) { + if (chan > 0x47) + return 1; + + if (lvl & 0x80) + return 3; + + chan -= 0x40; + TownsAudio_PcmChannel *p = &_pcmChan[chan]; + + if (_pcmChanReserved & _chanFlags[chan]) { + _pcmChanVelo[chan] = lvl; + p->velo = lvl << 1; + } else { + int32 t = p->envStep * lvl; + if (_pcmChanLevel[chan]) + t /= _pcmChanLevel[chan]; + p->envStep = t; + t = p->envCurrentLevel * lvl; + if (_pcmChanLevel[chan]) + t /= _pcmChanLevel[chan]; + p->envCurrentLevel = t; + _pcmChanLevel[chan] = lvl; + p->velo = p->envCurrentLevel >> 8; + } + + return 0; +} + +void TownsAudioInterface::pcmUpdateEnvelopeGenerator(int chan) { + TownsAudio_PcmChannel *p = &_pcmChan[chan]; + if (!p->envCurrentLevel) { + _pcmChanKeyPlaying &= ~_chanFlags[chan]; + p->envState = kEnvReady; + } + + if (!(_pcmChanKeyPlaying & _chanFlags[chan])) + return; + + switch (p->envState) { + case kEnvAttacking: + if (((p->envCurrentLevel + p->envStep) >> 8) > p->envTotalLevel) { + p->envDecay(); + return; + } else { + p->envCurrentLevel += p->envStep; + } + break; + + case kEnvDecaying: + if (((p->envCurrentLevel - p->envStep) >> 8) < p->envSustainLevel) { + p->envSustain(); + return; + } else { + p->envCurrentLevel -= p->envStep; + } + break; + + case kEnvSustaining: + case kEnvReleasing: + p->envCurrentLevel -= p->envStep; + if (p->envCurrentLevel <= 0) + p->envCurrentLevel = 0; + break; + + default: + break; + } + p->velo = (p->envCurrentLevel >> 8) << 1; +} + +void TownsAudioInterface::pcmCalcPhaseStep(TownsAudio_PcmChannel *p, TownsAudio_WaveTable *w) { + int8 diff = p->note - w->baseNote; + uint16 r = w->rate + w->rateOffs; + uint16 bl = 0; + uint32 s = 0; + + if (diff < 0) { + diff *= -1; + bl = diff % 12; + diff /= 12; + s = (r >> diff); + if (bl) + s = (s * _pcmPhase2[bl]) >> 16; + + } else if (diff > 0) { + bl = diff % 12; + diff /= 12; + s = (r << diff); + if (bl) + s += ((s * _pcmPhase1[bl]) >> 16); + + } else { + s = r; + } + + p->stepNote = s & 0xffff; + p->step = (s * p->stepPitch) >> 14; +} + +void TownsAudioInterface::updateOutputVolume() { + // FM Towns seems to support volumes of 0 - 63 for each channel. + // We recalculate sane values for our 0 to 255 volume range and + // balance values for our -128 to 127 volume range + + // CD-AUDIO + uint32 maxVol = MAX(_outputLevel[12], _outputLevel[13]); + + int volume = (int)(((float)(maxVol * 255) / 63.0f)); + int balance = maxVol ? (int)( ( ((int)_outputLevel[13] - _outputLevel[12]) * 127) / (float)maxVol) : 0; + + g_system->getAudioCDManager()->setVolume(volume); + g_system->getAudioCDManager()->setBalance(balance); +} + +const uint8 TownsAudioInterface::_chanFlags[] = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 +}; + +const uint16 TownsAudioInterface::_frequency[] = { + 0x028C, 0x02B4, 0x02DC, 0x030A, 0x0338, 0x0368, 0x039C, 0x03D4, 0x040E, 0x044A, 0x048C, 0x04D0 +}; + +const uint8 TownsAudioInterface::_carrier[] = { + 0x10, 0x10, 0x10, 0x10, 0x30, 0x70, 0x70, 0xF0 +}; + +const uint8 TownsAudioInterface::_fmDefaultInstrument[] = { + 0x45, 0x4C, 0x45, 0x50, 0x49, 0x41, 0x4E, 0x4F, 0x01, 0x0A, 0x02, 0x01, + 0x1E, 0x32, 0x05, 0x00, 0x9C, 0xDC, 0x9C, 0xDC, 0x07, 0x03, 0x14, 0x08, + 0x00, 0x03, 0x05, 0x05, 0x55, 0x45, 0x27, 0xA7, 0x04, 0xC0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const uint16 TownsAudioInterface::_pcmPhase1[] = { + 0x879B, 0x0F37, 0x1F58, 0x306E, 0x4288, 0x55B6, 0x6A08, 0x7F8F, 0x965E, 0xAE88, 0xC882, 0xE341 +}; + +const uint16 TownsAudioInterface::_pcmPhase2[] = { + 0xFEFE, 0xF1A0, 0xE411, 0xD744, 0xCB2F, 0xBFC7, 0xB504, 0xAAE2, 0xA144, 0x9827, 0x8FAC +}; + +TownsAudio_PcmChannel::TownsAudio_PcmChannel() { + extData = 0; + clear(); +} + +TownsAudio_PcmChannel::~TownsAudio_PcmChannel() { + clear(); +} + +void TownsAudio_PcmChannel::loadExtData(uint8 *buffer, uint32 size) { + delete[] extData; + extData = new int8[size]; + int8 *src = (int8 *)buffer; + int8 *dst = extData; + for (uint32 i = 0; i < size; i++) + *dst++ = *src & 0x80 ? (*src++ & 0x7f) : -*src++; + + data = extData; + dataEnd = extData + size; + pos = 0; +} + +void TownsAudio_PcmChannel::setupLoop(uint32 start, uint32 len) { + loopLen = len << 11; + loopEnd = loopLen ? &data[(start + loopLen) >> 11] : dataEnd; + pos = start; +} + +void TownsAudio_PcmChannel::clear() { + curInstrument = 0; + note = 0; + velo = 0; + + data = 0; + dataEnd = 0; + loopLen = 0; + + pos = 0; + loopEnd = 0; + + step = 0; + stepNote = 0x4000; + stepPitch = 0x4000; + + panLeft = panRight = 7; + + envTotalLevel = envAttackRate = envDecayRate = envSustainLevel = envSustainRate = envReleaseRate = 0; + envStep = envCurrentLevel = 0; + + envState = kEnvReady; + + delete[] extData; + extData = 0; +} + +void TownsAudio_PcmChannel::envAttack() { + envState = kEnvAttacking; + int16 t = envTotalLevel << 8; + if (envAttackRate == 127) { + envStep = 0; + } else if (envAttackRate) { + envStep = t / envAttackRate; + envCurrentLevel = 1; + } else { + envCurrentLevel = t; + envDecay(); + } +} + +void TownsAudio_PcmChannel::envDecay() { + envState = kEnvDecaying; + int16 t = envTotalLevel - envSustainLevel; + if (t < 0 || envDecayRate == 127) { + envStep = 0; + } else if (envDecayRate) { + envStep = (t << 8) / envDecayRate; + } else { + envCurrentLevel = envSustainLevel << 8; + envSustain(); + } +} + +void TownsAudio_PcmChannel::envSustain() { + envState = kEnvSustaining; + if (envSustainLevel && envSustainRate) + envStep = (envSustainRate == 127) ? 0 : (envCurrentLevel / envSustainRate) >> 1; + else + envStep = envCurrentLevel = 1; +} + +void TownsAudio_PcmChannel::envRelease() { + envState = kEnvReleasing; + if (envReleaseRate == 127) + envStep = 0; + else if (envReleaseRate) + envStep = envCurrentLevel / envReleaseRate; + else + envStep = envCurrentLevel = 1; +} + +TownsAudio_WaveTable::TownsAudio_WaveTable() { + data = 0; + clear(); +} + +TownsAudio_WaveTable::~TownsAudio_WaveTable() { + clear(); +} + +void TownsAudio_WaveTable::readHeader(const uint8 *buffer) { + memcpy(name, buffer, 8); + name[8] = 0; + id = READ_LE_UINT32(&buffer[8]); + size = READ_LE_UINT32(&buffer[12]); + loopStart = READ_LE_UINT32(&buffer[16]); + loopLen = READ_LE_UINT32(&buffer[20]); + rate = READ_LE_UINT16(&buffer[24]); + rateOffs = READ_LE_UINT16(&buffer[26]); + baseNote = READ_LE_UINT32(&buffer[28]); +} + +void TownsAudio_WaveTable::readData(const uint8 *buffer) { + if (!size) + return; + + delete[] data; + data = new int8[size]; + + const int8 *src = (const int8 *)buffer; + int8 *dst = data; + for (uint32 i = 0; i < size; i++) + *dst++ = *src & 0x80 ? (*src++ & 0x7f) : -*src++; +} + +void TownsAudio_WaveTable::clear() { + name[0] = name[8] = 0; + id = -1; + size = 0; + loopStart = 0; + loopLen = 0; + rate = 0; + rateOffs = 0; + baseNote = 0; + delete[] data; + data = 0; +} + diff --git a/audio/softsynth/fmtowns_pc98/towns_audio.h b/audio/softsynth/fmtowns_pc98/towns_audio.h new file mode 100644 index 0000000000..2819ab2d57 --- /dev/null +++ b/audio/softsynth/fmtowns_pc98/towns_audio.h @@ -0,0 +1,179 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TOWNS_AUDIO_H +#define TOWNS_AUDIO_H + +#include "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h" + +class TownsAudioInterfacePluginDriver { +public: + virtual ~TownsAudioInterfacePluginDriver() {} + virtual void timerCallback(int timerId) = 0; +}; + +class TownsAudio_PcmChannel; +class TownsAudio_WaveTable; + +class TownsAudioInterface : public TownsPC98_FmSynth { +public: + TownsAudioInterface(Audio::Mixer *mixer, TownsAudioInterfacePluginDriver *driver); + ~TownsAudioInterface(); + + bool init(); + + int callback(int command, ...); + + void setMusicVolume(int volume); + void setSoundEffectVolume(int volume); + // Defines the channels used as sound effect channels for the purpose of ScummVM GUI volume control. + // The first 6 bits are the 6 fm channels. The next 8 bits are pcm channels. + void setSoundEffectChanMask(int mask); + +private: + void nextTickEx(int32 *buffer, uint32 bufferSize); + + void timerCallbackA(); + void timerCallbackB(); + + typedef int (TownsAudioInterface::*TownsAudioIntfCallback)(va_list &); + const TownsAudioIntfCallback *_intfOpcodes; + + int intf_reset(va_list &args); + int intf_keyOn(va_list &args); + int intf_keyOff(va_list &args); + int intf_setPanPos(va_list &args); + int intf_setInstrument(va_list &args); + int intf_loadInstrument(va_list &args); + int intf_setPitch(va_list &args); + int intf_setLevel(va_list &args); + int intf_chanOff(va_list &args); + int intf_writeReg(va_list &args); + int intf_writeRegBuffer(va_list &args); + int intf_readRegBuffer(va_list &args); + int intf_setTimerA(va_list &args); + int intf_setTimerB(va_list &args); + int intf_enableTimerA(va_list &args); + int intf_enableTimerB(va_list &args); + int intf_loadSamples(va_list &args); + int intf_reserveEffectChannels(va_list &args); + int intf_loadWaveTable(va_list &args); + int intf_unloadWaveTable(va_list &args); + int intf_pcmPlayEffect(va_list &args); + int intf_pcmChanOff(va_list &args); + int intf_pcmEffectPlaying(va_list &args); + int intf_fmKeyOn(va_list &args); + int intf_fmKeyOff(va_list &args); + int intf_fmSetPanPos(va_list &args); + int intf_fmSetInstrument(va_list &args); + int intf_fmLoadInstrument(va_list &args); + int intf_fmSetPitch(va_list &args); + int intf_fmSetLevel(va_list &args); + int intf_fmReset(va_list &args); + int intf_setOutputVolume(va_list &args); + int intf_resetOutputVolume(va_list &args); + int intf_updateOutputVolume(va_list &args); + int intf_cdaToggle(va_list &args); + int intf_pcmUpdateEnvelopeGenerator(va_list &args); + + int intf_notImpl(va_list &args); + + void fmReset(); + int fmKeyOn(int chan, int note, int velo); + int fmKeyOff(int chan); + int fmChanOff(int chan); + int fmSetPanPos(int chan, int mode); + int fmSetInstrument(int chan, int instrId); + int fmLoadInstrument(int instrId, const uint8 *data); + int fmSetPitch(int chan, int pitch); + int fmSetLevel(int chan, int lvl); + + void bufferedWriteReg(uint8 part, uint8 regAddress, uint8 value); + + uint8 _fmChanPlaying; + uint8 _fmChanNote[6]; + int16 _fmChanPitch[6]; + + uint8 *_fmSaveReg[2]; + uint8 *_fmInstruments; + + void pcmReset(); + int pcmKeyOn(int chan, int note, int velo); + int pcmKeyOff(int chan); + int pcmChanOff(int chan); + int pcmSetPanPos(int chan, int mode); + int pcmSetInstrument(int chan, int instrId); + int pcmLoadInstrument(int instrId, const uint8 *data); + int pcmSetPitch(int chan, int pitch); + int pcmSetLevel(int chan, int lvl); + void pcmUpdateEnvelopeGenerator(int chan); + + TownsAudio_PcmChannel *_pcmChan; + uint8 _pcmChanOut; + uint8 _pcmChanReserved; + uint8 _pcmChanKeyPressed; + uint8 _pcmChanEffectPlaying; + uint8 _pcmChanKeyPlaying; + + uint8 _pcmChanNote[8]; + uint8 _pcmChanVelo[8]; + uint8 _pcmChanLevel[8]; + + uint8 _numReservedChannels; + uint8 *_pcmInstruments; + + TownsAudio_WaveTable *_waveTables; + uint8 _numWaveTables; + uint32 _waveTablesTotalDataSize; + + void pcmCalcPhaseStep(TownsAudio_PcmChannel *p, TownsAudio_WaveTable *w); + + void updateOutputVolume(); + uint8 _outputVolumeFlags; + uint8 _outputLevel[16]; + uint8 _outputMuteFlags; + + const float _baserate; + uint32 _timerBase; + uint32 _tickLength; + uint32 _timer; + + uint16 _musicVolume; + uint16 _sfxVolume; + int _pcmSfxChanMask; + + TownsAudioInterfacePluginDriver *_drv; + bool _ready; + + static const uint8 _chanFlags[]; + static const uint16 _frequency[]; + static const uint8 _carrier[]; + static const uint8 _fmDefaultInstrument[]; + static const uint16 _pcmPhase1[]; + static const uint16 _pcmPhase2[]; +}; + +#endif + diff --git a/audio/softsynth/fmtowns_pc98/towns_euphony.cpp b/audio/softsynth/fmtowns_pc98/towns_euphony.cpp new file mode 100644 index 0000000000..7c071c43fb --- /dev/null +++ b/audio/softsynth/fmtowns_pc98/towns_euphony.cpp @@ -0,0 +1,905 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/softsynth/fmtowns_pc98/towns_euphony.h" +#include "common/endian.h" + +TownsEuphonyDriver::TownsEuphonyDriver(Audio::Mixer *mixer) : _activeChannels(0), _sustainChannels(0), + _assignedChannels(0), _paraCount(0), _command(0), _tEnable(0), _tMode(0), _tOrdr(0), _tLevel(0), + _tTranspose(0), _musicPos(0), _musicStart(0), _playing(false), _eventBuffer(0), _bufferedEventsCount(0), + _tempoControlMode(0) { + _para[0] = _para[1] = 0; + _intf = new TownsAudioInterface(mixer, this); + resetTempo(); +} + +TownsEuphonyDriver::~TownsEuphonyDriver() { + delete _intf; + delete[] _activeChannels; + delete[] _sustainChannels; + delete[] _assignedChannels; + delete[] _tEnable; + delete[] _tMode; + delete[] _tOrdr; + delete[] _tLevel; + delete[] _tTranspose; +} + +bool TownsEuphonyDriver::init() { + if (!_intf->init()) + return false; + + _activeChannels = new int8[16]; + _sustainChannels = new int8[16]; + _assignedChannels = new ActiveChannel[128]; + _eventBuffer = new DlEvent[64]; + + _tEnable = new uint8[32]; + _tMode = new uint8[32]; + _tOrdr = new uint8[32]; + _tLevel = new int8[32]; + _tTranspose = new int8[32]; + + reset(); + + return true; +} + +void TownsEuphonyDriver::reset() { + _intf->callback(0); + + _intf->callback(74); + _intf->callback(70); + _intf->callback(75, 3); + + setTimerA(true, 1); + setTimerA(false, 1); + setTimerB(true, 221); + + _paraCount = _command = _para[0] = _para[1] = 0; + memset(_sustainChannels, 0, 16); + memset(_activeChannels, -1, 16); + for (int i = 0; i < 128; i++) { + _assignedChannels[i].chan = _assignedChannels[i].next = -1; + _assignedChannels[i].note = _assignedChannels[i].sub = 0; + } + + int e = 0; + for (int i = 0; i < 6; i++) + assignChannel(i, e++); + for (int i = 0x40; i < 0x48; i++) + assignChannel(i, e++); + + resetTables(); + + memset(_eventBuffer, 0, 64 * sizeof(DlEvent)); + _bufferedEventsCount = 0; + + _playing = _endOfTrack = _suspendParsing = _loop = false; + _elapsedEvents = 0; + _tempoDiff = 0; + + resetTempo(); + + if (_tempoControlMode == 1) { + //if (///) + // return; + setTempoIntern(_defaultTempo); + } else { + setTempoIntern(_defaultTempo); + } + + resetControl(); +} + +void TownsEuphonyDriver::loadInstrument(int chanType, int id, const uint8 *data) { + _intf->callback(5, chanType, id, data); +} + +void TownsEuphonyDriver::loadWaveTable(const uint8 *data) { + _intf->callback(34, data); +} + +void TownsEuphonyDriver::unloadWaveTable(int id) { + _intf->callback(35, id); +} + +void TownsEuphonyDriver::reserveSoundEffectChannels(int num) { + _intf->callback(33, num); + uint32 volMask = 0; + + if (num > 8) + return; + + for (uint32 v = 1 << 13; num; num--) { + volMask |= v; + v >>= 1; + } + + _intf->setSoundEffectChanMask(volMask); +} + +int TownsEuphonyDriver::setMusicTempo(int tempo) { + if (tempo > 250) + return 3; + _defaultTempo = tempo; + _trackTempo = tempo; + setTempoIntern(tempo); + return 0; +} + +int TownsEuphonyDriver::startMusicTrack(const uint8 *data, int trackSize, int startTick) { + if (_playing) + return 2; + + _musicPos = _musicStart = data; + _defaultBaseTickLen = _baseTickLen = startTick; + _musicTrackSize = trackSize; + _timeStampBase = _timeStampDest = 0; + _tickCounter = 0; + _playing = true; + + return 0; +} + +void TownsEuphonyDriver::setMusicLoop(bool loop) { + _loop = loop; +} + +void TownsEuphonyDriver::stopParser() { + if (_playing) { + _playing = false; + _pulseCount = 0; + _endOfTrack = false; + flushEventBuffer(); + resetControl(); + } +} + +void TownsEuphonyDriver::continueParsing() { + _suspendParsing = false; +} + +void TownsEuphonyDriver::playSoundEffect(int chan, int note, int velo, const uint8 *data) { + _intf->callback(37, chan, note, velo, data); +} + +void TownsEuphonyDriver::stopSoundEffect(int chan) { + _intf->callback(39, chan); +} + +bool TownsEuphonyDriver::soundEffectIsPlaying(int chan) { + return _intf->callback(40, chan) ? true : false; +} + +void TownsEuphonyDriver::chanPanPos(int chan, int mode) { + _intf->callback(3, chan, mode); +} + +void TownsEuphonyDriver::chanPitch(int chan, int pitch) { + _intf->callback(7, chan, pitch); +} + +void TownsEuphonyDriver::chanVolume(int chan, int vol) { + _intf->callback(8, chan, vol); +} + +void TownsEuphonyDriver::setOutputVolume(int mode, int volLeft, int volRight) { + _intf->callback(67, mode, volLeft, volRight); +} + +int TownsEuphonyDriver::chanEnable(int tableEntry, int val) { + if (tableEntry > 31) + return 3; + _tEnable[tableEntry] = val; + return 0; +} + +int TownsEuphonyDriver::chanMode(int tableEntry, int val) { + if (tableEntry > 31) + return 3; + _tMode[tableEntry] = val; + return 0; +} + +int TownsEuphonyDriver::chanOrdr(int tableEntry, int val) { + if (tableEntry > 31) + return 3; + if (val < 16) + _tOrdr[tableEntry] = val; + return 0; +} + +int TownsEuphonyDriver::chanVolumeShift(int tableEntry, int val) { + if (tableEntry > 31) + return 3; + if (val <= 40) + _tLevel[tableEntry] = (int8)(val & 0xff); + return 0; +} + +int TownsEuphonyDriver::chanNoteShift(int tableEntry, int val) { + if (tableEntry > 31) + return 3; + if (val <= 40) + _tTranspose[tableEntry] = (int8)(val & 0xff); + return 0; +} + +int TownsEuphonyDriver::assignChannel(int chan, int tableEntry) { + if (tableEntry > 15 || chan > 127 || chan < 0) + return 3; + + ActiveChannel *a = &_assignedChannels[chan]; + if (a->chan == tableEntry) + return 0; + + if (a->chan != -1) { + int8 *b = &_activeChannels[a->chan]; + while (*b != chan) { + b = &_assignedChannels[*b].next; + if (*b == -1 && *b != chan) + return 3; + } + + *b = a->next; + + if (a->note) + _intf->callback(2, chan); + + a->chan = a->next = -1; + a->note = 0; + } + + a->next = _activeChannels[tableEntry]; + _activeChannels[tableEntry] = chan; + a->chan = tableEntry; + a->note = a->sub = 0; + + return 0; +} + +void TownsEuphonyDriver::timerCallback(int timerId) { + switch (timerId) { + case 0: + updatePulseCount(); + while (_pulseCount > 0) { + --_pulseCount; + updateTimeStampBase(); + if (!_playing) + continue; + updateEventBuffer(); + updateParser(); + updateCheckEot(); + } + break; + default: + break; + } +} + +void TownsEuphonyDriver::setMusicVolume(int volume) { + _intf->setMusicVolume(volume); +} + +void TownsEuphonyDriver::setSoundEffectVolume(int volume) { + _intf->setSoundEffectVolume(volume); +} + +void TownsEuphonyDriver::resetTables() { + memset(_tEnable, 0xff, 32); + memset(_tMode, 0xff, 16); + memset(_tMode + 16, 0, 16); + for (int i = 0; i < 32; i++) + _tOrdr[i] = i & 0x0f; + memset(_tLevel, 0, 32); + memset(_tTranspose, 0, 32); +} + +void TownsEuphonyDriver::resetTempo() { + _defaultBaseTickLen = _baseTickLen = 0x33; + _pulseCount = 0; + _extraTimingControlRemainder = 0; + _extraTimingControl = 16; + _tempoModifier = 0; + _timeStampDest = 0; + _deltaTicks = 0; + _tickCounter = 0; + _defaultTempo = 90; + _trackTempo = 90; +} + +void TownsEuphonyDriver::setTempoIntern(int tempo) { + tempo = CLIP(tempo + _tempoModifier, 0, 500); + if (_tempoControlMode == 0) { + _timerSetting = 34750 / (tempo + 30); + _extraTimingControl = 16; + + while (_timerSetting < 126) { + _timerSetting <<= 1; + _extraTimingControl <<= 1; + } + + while (_timerSetting > 383) { + _timerSetting >>= 1; + _extraTimingControl >>= 1; + } + + setTimerA(true, -(_timerSetting - 2)); + + } else if (_tempoControlMode == 1) { + _timerSetting = 312500 / (tempo + 30); + _extraTimingControl = 16; + while (_timerSetting < 1105) { + _timerSetting <<= 1; + _extraTimingControl <<= 1; + } + + } else if (_tempoControlMode == 2) { + _timerSetting = 625000 / (tempo + 30); + _extraTimingControlRemainder = 0; + } +} + +void TownsEuphonyDriver::setTimerA(bool enable, int tempo) { + _intf->callback(21, enable ? 255 : 0, tempo); +} + +void TownsEuphonyDriver::setTimerB(bool enable, int tempo) { + _intf->callback(22, enable ? 255 : 0, tempo); +} + +void TownsEuphonyDriver::updatePulseCount() { + int tc = _extraTimingControl + _extraTimingControlRemainder; + _extraTimingControlRemainder = tc & 0x0f; + tc >>= 4; + _tempoDiff -= tc; + + while (_tempoDiff < 0) { + _elapsedEvents++; + _tempoDiff += 4; + } + + if (_playing && !_suspendParsing) + _pulseCount += tc; +} + +void TownsEuphonyDriver::updateTimeStampBase() { + static const uint16 table[] = { 0x180, 0xC0, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18 }; + if ((uint32)(table[_baseTickLen >> 4] * ((_baseTickLen & 0x0f) + 1)) > ++_tickCounter) + return; + ++_timeStampDest; + _tickCounter = 0; + _deltaTicks = 0; +} + +void TownsEuphonyDriver::updateParser() { + for (bool loop = true; loop;) { + uint8 cmd = _musicPos[0]; + + if (cmd == 0xff || cmd == 0xf7) { + jumpNextLoop(); + + } else if (cmd < 0x90) { + _endOfTrack = true; + flushEventBuffer(); + loop = false; + + } else if (_timeStampBase > _timeStampDest) { + loop = false; + + } else { + if (_timeStampBase == _timeStampDest) { + uint16 timeStamp = READ_LE_UINT16(&_musicPos[2]); + uint8 l = (timeStamp & 0xff) + (timeStamp & 0xff); + timeStamp = ((timeStamp & 0xff00) | l) >> 1; + if (timeStamp > _tickCounter) + loop = false; + } + + if (loop) { + if (parseNext()) + loop = false; + } + } + } +} + +void TownsEuphonyDriver::updateCheckEot() { + if (!_endOfTrack || _bufferedEventsCount) + return; + stopParser(); +} + +bool TownsEuphonyDriver::parseNext() { +#define OPC(x) &TownsEuphonyDriver::evt##x + static const EuphonyOpcode opcodes[] = { + OPC(NotImpl), + OPC(SetupNote), + OPC(PolyphonicAftertouch), + OPC(ControlPitch), + OPC(InstrumentChanAftertouch), + OPC(InstrumentChanAftertouch), + OPC(ControlPitch) + }; +#undef OPC + + uint cmd = _musicPos[0]; + if (cmd != 0xfe && cmd != 0xfd) { + if (cmd >= 0xf0) { + cmd &= 0x0f; + if (cmd == 0) + evtLoadInstrument(); + else if (cmd == 2) + evtAdvanceTimestampOffset(); + else if (cmd == 8) + evtTempo(); + else if (cmd == 12) + evtModeOrdrChange(); + jumpNextLoop(); + return false; + + } else if (!(this->*opcodes[(cmd - 0x80) >> 4])()) { + jumpNextLoop(); + return false; + } + } + + if (cmd == 0xfd) { + _suspendParsing = true; + return true; + } + + if (!_loop) { + _endOfTrack = true; + return true; + } + + _endOfTrack = false; + _musicPos = _musicStart; + _timeStampBase = _timeStampDest = _tickCounter = 0; + _baseTickLen = _defaultBaseTickLen; + + return false; +} + +void TownsEuphonyDriver::jumpNextLoop() { + _musicPos += 6; + if (_musicPos >= _musicStart + _musicTrackSize) + _musicPos = _musicStart; +} + +void TownsEuphonyDriver::updateEventBuffer() { + DlEvent *e = _eventBuffer; + for (int i = _bufferedEventsCount; i; e++) { + if (e->evt == 0) + continue; + if (--e->len) { + --i; + continue; + } + processBufferNote(e->mode, e->evt, e->note, e->velo); + e->evt = 0; + --i; + --_bufferedEventsCount; + } +} + +void TownsEuphonyDriver::flushEventBuffer() { + DlEvent *e = _eventBuffer; + for (int i = _bufferedEventsCount; i; e++) { + if (e->evt == 0) + continue; + processBufferNote(e->mode, e->evt, e->note, e->velo); + e->evt = 0; + --i; + --_bufferedEventsCount; + } +} + +void TownsEuphonyDriver::processBufferNote(int mode, int evt, int note, int velo) { + if (!velo) + evt &= 0x8f; + sendEvent(mode, evt); + sendEvent(mode, note); + sendEvent(mode, velo); +} + +void TownsEuphonyDriver::resetControl() { + for (int i = 0; i < 32; i++) { + if (_tOrdr[i] > 15) { + for (int ii = 0; ii < 16; ii++) + resetControlIntern(_tMode[i], ii); + } else { + resetControlIntern(_tMode[i], _tOrdr[i]); + } + } +} + +void TownsEuphonyDriver::resetControlIntern(int mode, int chan) { + sendEvent(mode, 0xb0 | chan); + sendEvent(mode, 0x40); + sendEvent(mode, 0); + sendEvent(mode, 0xb0 | chan); + sendEvent(mode, 0x7b); + sendEvent(mode, 0); + sendEvent(mode, 0xb0 | chan); + sendEvent(mode, 0x79); + sendEvent(mode, 0x40); +} + +uint8 TownsEuphonyDriver::appendEvent(uint8 evt, uint8 chan) { + if (evt >= 0x80 && evt < 0xf0 && _tOrdr[chan] < 16) + return (evt & 0xf0) | _tOrdr[chan]; + return evt; +} + +void TownsEuphonyDriver::sendEvent(uint8 mode, uint8 command) { + if (mode == 0) { + // warning("TownsEuphonyDriver: Mode 0 not implemented"); + + } else if (mode == 0x10) { + warning("TownsEuphonyDriver: Mode 0x10 not implemented"); + + } else if (mode == 0xff) { + if (command >= 0xf0) { + _paraCount = 1; + _command = 0; + } else if (command >= 0x80) { + _paraCount = 1; + _command = command; + } else if (_command >= 0x80) { + switch ((_command - 0x80) >> 4) { + case 0: + if (_paraCount < 2) { + _paraCount++; + _para[0] = command; + } else { + _paraCount = 1; + _para[1] = command; + sendNoteOff(); + } + break; + + case 1: + if (_paraCount < 2) { + _paraCount++; + _para[0] = command; + } else { + _paraCount = 1; + _para[1] = command; + if (command) + sendNoteOn(); + else + sendNoteOff(); + } + break; + + case 2: + if (_paraCount < 2) { + _paraCount++; + _para[0] = command; + } else { + _paraCount = 1; + } + break; + + case 3: + if (_paraCount < 2) { + _paraCount++; + _para[0] = command; + } else { + _paraCount = 1; + _para[1] = command; + + if (_para[0] == 7) + sendChanVolume(); + else if (_para[0] == 10) + sendPanPosition(); + else if (_para[0] == 64) + sendAllNotesOff(); + } + break; + + case 4: + _paraCount = 1; + _para[0] = command; + sendSetInstrument(); + break; + + case 5: + _paraCount = 1; + _para[0] = command; + break; + + case 6: + if (_paraCount < 2) { + _paraCount++; + _para[0] = command; + } else { + _paraCount = 1; + _para[1] = command; + sendPitch(); + } + break; + } + } + } +} + +bool TownsEuphonyDriver::evtSetupNote() { + if (_musicPos[1] > 31) + return false; + if (!_tEnable[_musicPos[1]]) { + jumpNextLoop(); + return (_musicPos[0] == 0xfe || _musicPos[0] == 0xfd) ? true : false; + } + uint8 evt = appendEvent(_musicPos[0], _musicPos[1]); + uint8 mode = _tMode[_musicPos[1]]; + uint8 note = _musicPos[4]; + uint8 velo = _musicPos[5]; + + sendEvent(mode, evt); + sendEvent(mode, applyNoteShift(note)); + sendEvent(mode, applyVolumeShift(velo)); + + jumpNextLoop(); + if (_musicPos[0] == 0xfe || _musicPos[0] == 0xfd) + return true; + + velo = _musicPos[5]; + uint16 len = ((((_musicPos[1] << 4) | (_musicPos[2] << 8)) >> 4) & 0xff) | ((((_musicPos[3] << 4) | (_musicPos[4] << 8)) >> 4) << 8); + + int i = 0; + for (; i < 64; i++) { + if (_eventBuffer[i].evt == 0) + break; + } + + if (i == 64) { + processBufferNote(mode, evt, note, velo); + } else { + _eventBuffer[i].evt = evt; + _eventBuffer[i].mode = mode; + _eventBuffer[i].note = note; + _eventBuffer[i].velo = velo; + _eventBuffer[i].len = len ? len : 1; + _bufferedEventsCount++; + } + + return false; +} + +bool TownsEuphonyDriver::evtPolyphonicAftertouch() { + if (_musicPos[1] > 31) + return false; + if (!_tEnable[_musicPos[1]]) + return false; + + uint8 evt = appendEvent(_musicPos[0], _musicPos[1]); + uint8 mode = _tMode[_musicPos[1]]; + + sendEvent(mode, evt); + sendEvent(mode, applyNoteShift(_musicPos[4])); + sendEvent(mode, _musicPos[5]); + + return false; +} + +bool TownsEuphonyDriver::evtControlPitch() { + if (_musicPos[1] > 31) + return false; + if (!_tEnable[_musicPos[1]]) + return false; + + uint8 evt = appendEvent(_musicPos[0], _musicPos[1]); + uint8 mode = _tMode[_musicPos[1]]; + + sendEvent(mode, evt); + sendEvent(mode, _musicPos[4]); + sendEvent(mode, _musicPos[5]); + + return false; +} + +bool TownsEuphonyDriver::evtInstrumentChanAftertouch() { + if (_musicPos[1] > 31) + return false; + if (!_tEnable[_musicPos[1]]) + return false; + + uint8 evt = appendEvent(_musicPos[0], _musicPos[1]); + uint8 mode = _tMode[_musicPos[1]]; + + sendEvent(mode, evt); + sendEvent(mode, _musicPos[4]); + + return false; +} + +bool TownsEuphonyDriver::evtLoadInstrument() { + return false; +} + +bool TownsEuphonyDriver::evtAdvanceTimestampOffset() { + ++_timeStampBase; + _baseTickLen = _musicPos[1]; + return false; +} + +bool TownsEuphonyDriver::evtTempo() { + uint8 l = _musicPos[4] << 1; + _trackTempo = (l | (_musicPos[5] << 8)) >> 1; + setTempoIntern(_trackTempo); + return false; +} + +bool TownsEuphonyDriver::evtModeOrdrChange() { + if (_musicPos[1] > 31) + return false; + if (!_tEnable[_musicPos[1]]) + return false; + + if (_musicPos[4] == 1) + _tMode[_musicPos[1]] = _musicPos[5]; + else if (_musicPos[4] == 2) + _tOrdr[_musicPos[1]] = _musicPos[5]; + + return false; +} + +uint8 TownsEuphonyDriver::applyNoteShift(uint8 in) { + int out = _tTranspose[_musicPos[1]]; + if (!out) + return in; + out += (in & 0x7f); + + if (out > 127) + out -= 12; + + if (out < 0) + out += 12; + + return out & 0xff; +} + +uint8 TownsEuphonyDriver::applyVolumeShift(uint8 in) { + int out = _tLevel[_musicPos[1]]; + out += (in & 0x7f); + out = CLIP(out, 1, 127); + + return out & 0xff; +} + +void TownsEuphonyDriver::sendNoteOff() { + int8 *chan = &_activeChannels[_command & 0x0f]; + if (*chan == -1) + return; + + while (_assignedChannels[*chan].note != _para[0]) { + chan = &_assignedChannels[*chan].next; + if (*chan == -1) + return; + } + + if (_sustainChannels[_command & 0x0f]) { + _assignedChannels[*chan].note |= 0x80; + } else { + _assignedChannels[*chan].note = 0; + _intf->callback(2, *chan); + } +} + +void TownsEuphonyDriver::sendNoteOn() { + if (!_para[0]) + return; + int8 *chan = &_activeChannels[_command & 0x0f]; + if (*chan == -1) + return; + + do { + _assignedChannels[*chan].sub++; + chan = &_assignedChannels[*chan].next; + } while (*chan != -1); + + chan = &_activeChannels[_command & 0x0f]; + + int d = 0; + int c = 0; + bool found = false; + + do { + if (!_assignedChannels[*chan].note) { + found = true; + break; + } + if (d <= _assignedChannels[*chan].sub) { + c = *chan; + d = _assignedChannels[*chan].sub; + } + chan = &_assignedChannels[*chan].next; + } while (*chan != -1); + + if (found) + c = *chan; + else + _intf->callback(2, c); + + _assignedChannels[c].note = _para[0]; + _assignedChannels[c].sub = 0; + _intf->callback(1, c, _para[0], _para[1]); +} + +void TownsEuphonyDriver::sendChanVolume() { + int8 *chan = &_activeChannels[_command & 0x0f]; + while (*chan != -1) { + _intf->callback(8, *chan, _para[1] & 0x7f); + chan = &_assignedChannels[*chan].next; + } +} + +void TownsEuphonyDriver::sendPanPosition() { + int8 *chan = &_activeChannels[_command & 0x0f]; + while (*chan != -1) { + _intf->callback(3, *chan, _para[1] & 0x7f); + chan = &_assignedChannels[*chan].next; + } +} + +void TownsEuphonyDriver::sendAllNotesOff() { + if (_para[1] > 63) { + _sustainChannels[_command & 0x0f] = -1; + return; + } + + _sustainChannels[_command & 0x0f] = 0; + int8 *chan = &_activeChannels[_command & 0x0f]; + while (*chan != -1) { + if (_assignedChannels[*chan].note & 0x80) { + _assignedChannels[*chan].note = 0; + _intf->callback(2, *chan); + } + chan = &_assignedChannels[*chan].next; + } +} + +void TownsEuphonyDriver::sendSetInstrument() { + int8 *chan = &_activeChannels[_command & 0x0f]; + while (*chan != -1) { + _intf->callback(4, *chan, _para[0]); + _intf->callback(7, *chan, 0); + chan = &_assignedChannels[*chan].next; + } +} + +void TownsEuphonyDriver::sendPitch() { + int8 *chan = &_activeChannels[_command & 0x0f]; + while (*chan != -1) { + _para[0] += _para[0]; + int16 pitch = (((READ_LE_UINT16(_para)) >> 1) & 0x3fff) - 0x2000; + _intf->callback(7, *chan, pitch); + chan = &_assignedChannels[*chan].next; + } +} diff --git a/audio/softsynth/fmtowns_pc98/towns_euphony.h b/audio/softsynth/fmtowns_pc98/towns_euphony.h new file mode 100644 index 0000000000..dc40373913 --- /dev/null +++ b/audio/softsynth/fmtowns_pc98/towns_euphony.h @@ -0,0 +1,187 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TOWNS_EUP_H +#define TOWNS_EUP_H + +#include "audio/softsynth/fmtowns_pc98/towns_audio.h" + +class TownsEuphonyDriver : public TownsAudioInterfacePluginDriver { +public: + TownsEuphonyDriver(Audio::Mixer *mixer); + virtual ~TownsEuphonyDriver(); + + bool init(); + void reset(); + + void loadInstrument(int chanType, int id, const uint8 *data); + void loadWaveTable(const uint8 *data); + void unloadWaveTable(int id); + void reserveSoundEffectChannels(int num); + + int setMusicTempo(int tempo); + int startMusicTrack(const uint8 *data, int trackSize, int startTick); + void setMusicLoop(bool loop); + void stopParser(); + bool parserIsPlaying() {return _playing; } + void continueParsing(); + + void playSoundEffect(int chan, int note, int velo, const uint8 *data); + void stopSoundEffect(int chan); + bool soundEffectIsPlaying(int chan); + + void chanPanPos(int chan, int mode); + void chanPitch(int chan, int pitch); + void chanVolume(int chan, int vol); + + void setOutputVolume(int chanType, int volLeft, int volRight); + + int chanEnable(int tableEntry, int val); + int chanMode(int tableEntry, int val); + int chanOrdr(int tableEntry, int val); + int chanVolumeShift(int tableEntry, int val); + int chanNoteShift(int tableEntry, int val); + + int assignChannel(int chan, int tableEntry); + + void timerCallback(int timerId); + + void setMusicVolume(int volume); + void setSoundEffectVolume(int volume); + + TownsAudioInterface *intf() { + return _intf; + } + +private: + void resetTables(); + + void resetTempo(); + void setTempoIntern(int tempo); + void setTimerA(bool enable, int tempo); + void setTimerB(bool enable, int tempo); + + void updatePulseCount(); + void updateTimeStampBase(); + void updateParser(); + void updateCheckEot(); + + bool parseNext(); + void jumpNextLoop(); + + void updateEventBuffer(); + void flushEventBuffer(); + void processBufferNote(int mode, int evt, int note, int velo); + + void resetControl(); + void resetControlIntern(int mode, int chan); + uint8 appendEvent(uint8 evt, uint8 chan); + + void sendEvent(uint8 mode, uint8 command); + + typedef bool(TownsEuphonyDriver::*EuphonyOpcode)(); + bool evtSetupNote(); + bool evtPolyphonicAftertouch(); + bool evtControlPitch(); + bool evtInstrumentChanAftertouch(); + bool evtLoadInstrument(); + bool evtAdvanceTimestampOffset(); + bool evtTempo(); + bool evtModeOrdrChange(); + bool evtNotImpl() { + return false; + } + + uint8 applyNoteShift(uint8 in); + uint8 applyVolumeShift(uint8 in); + + void sendNoteOff(); + void sendNoteOn(); + void sendChanVolume(); + void sendPanPosition(); + void sendAllNotesOff(); + void sendSetInstrument(); + void sendPitch(); + + int8 *_activeChannels; + int8 *_sustainChannels; + + struct ActiveChannel { + int8 chan; + int8 next; + uint8 note; + uint8 sub; + } *_assignedChannels; + + uint8 *_tEnable; + uint8 *_tMode; + uint8 *_tOrdr; + int8 *_tLevel; + int8 *_tTranspose; + + struct DlEvent { + uint8 evt; + uint8 mode; + uint8 note; + uint8 velo; + uint16 len; + } *_eventBuffer; + int _bufferedEventsCount; + + uint8 _para[2]; + uint8 _paraCount; + uint8 _command; + + uint8 _defaultBaseTickLen; + uint8 _baseTickLen; + uint32 _pulseCount; + int _tempoControlMode; + int _extraTimingControlRemainder; + int _extraTimingControl; + int _timerSetting; + int8 _tempoDiff; + int _tempoModifier; + uint32 _timeStampDest; + uint32 _timeStampBase; + int8 _elapsedEvents; + uint8 _deltaTicks; + uint32 _tickCounter; + uint8 _defaultTempo; + int _trackTempo; + + bool _loop; + bool _playing; + bool _endOfTrack; + bool _suspendParsing; + + const uint8 *_musicStart; + const uint8 *_musicPos; + uint32 _musicTrackSize; + + TownsAudioInterface *_intf; +}; + +#endif + diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_driver.cpp b/audio/softsynth/fmtowns_pc98/towns_pc98_driver.cpp new file mode 100644 index 0000000000..79fd95ea0d --- /dev/null +++ b/audio/softsynth/fmtowns_pc98/towns_pc98_driver.cpp @@ -0,0 +1,1428 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/softsynth/fmtowns_pc98/towns_pc98_driver.h" +#include "common/endian.h" + +class TownsPC98_MusicChannel { +public: + TownsPC98_MusicChannel(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, + uint8 key, uint8 prt, uint8 id); + virtual ~TownsPC98_MusicChannel(); + virtual void init(); + + typedef enum channelState { + CHS_RECALCFREQ = 0x01, + CHS_KEYOFF = 0x02, + CHS_SSGOFF = 0x04, + CHS_VBROFF = 0x08, + CHS_ALLOFF = 0x0f, + CHS_PROTECT = 0x40, + CHS_EOT = 0x80 + } ChannelState; + + virtual void loadData(uint8 *data); + virtual void processEvents(); + virtual void processFrequency(); + virtual bool processControlEvent(uint8 cmd); + + virtual void keyOn(); + void keyOff(); + + void setOutputLevel(); + virtual void fadeStep(); + virtual void reset(); + + const uint8 _idFlag; + +protected: + void setupVibrato(); + bool processVibrato(); + + bool control_dummy(uint8 para); + bool control_f0_setPatch(uint8 para); + bool control_f1_presetOutputLevel(uint8 para); + bool control_f2_setKeyOffTime(uint8 para); + bool control_f3_setFreqLSB(uint8 para); + bool control_f4_setOutputLevel(uint8 para); + bool control_f5_setTempo(uint8 para); + bool control_f6_repeatSection(uint8 para); + bool control_f7_setupVibrato(uint8 para); + bool control_f8_toggleVibrato(uint8 para); + bool control_fa_writeReg(uint8 para); + virtual bool control_fb_incOutLevel(uint8 para); + virtual bool control_fc_decOutLevel(uint8 para); + bool control_fd_jump(uint8 para); + virtual bool control_ff_endOfTrack(uint8 para); + + uint8 _ticksLeft; + uint8 _algorithm; + uint8 _instr; + uint8 _totalLevel; + uint8 _frqBlockMSB; + int8 _frqLSB; + uint8 _keyOffTime; + bool _hold; + uint8 *_dataPtr; + uint8 _vbrInitDelayHi; + uint8 _vbrInitDelayLo; + int16 _vbrModInitVal; + uint8 _vbrDuration; + uint8 _vbrCurDelay; + int16 _vbrModCurVal; + uint8 _vbrDurLeft; + uint16 _frequency; + uint8 _block; + uint8 _regOffset; + uint8 _flags; + uint8 _ssgTl; + uint8 _ssgStep; + uint8 _ssgTicksLeft; + uint8 _ssgTargetLvl; + uint8 _ssgStartLvl; + + const uint8 _chanNum; + const uint8 _keyNum; + const uint8 _part; + + TownsPC98_AudioDriver *_drv; + + typedef bool (TownsPC98_MusicChannel::*ControlEventFunc)(uint8 para); + const ControlEventFunc *controlEvents; +}; + +class TownsPC98_MusicChannelSSG : public TownsPC98_MusicChannel { +public: + TownsPC98_MusicChannelSSG(TownsPC98_AudioDriver *driver, uint8 regOffs, + uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id); + virtual ~TownsPC98_MusicChannelSSG() {} + void init(); + + virtual void loadData(uint8 *data); + void processEvents(); + void processFrequency(); + bool processControlEvent(uint8 cmd); + + void keyOn(); + void nextShape(); + + void protect(); + void restore(); + virtual void reset(); + + void fadeStep(); + +protected: + void setOutputLevel(uint8 lvl); + + bool control_f0_setPatch(uint8 para); + bool control_f1_setTotalLevel(uint8 para); + bool control_f4_setAlgorithm(uint8 para); + bool control_f9_loadCustomPatch(uint8 para); + bool control_fb_incOutLevel(uint8 para); + bool control_fc_decOutLevel(uint8 para); + bool control_ff_endOfTrack(uint8 para); + + typedef bool (TownsPC98_MusicChannelSSG::*ControlEventFunc)(uint8 para); + const ControlEventFunc *controlEvents; +}; + +class TownsPC98_SfxChannel : public TownsPC98_MusicChannelSSG { +public: + TownsPC98_SfxChannel(TownsPC98_AudioDriver *driver, uint8 regOffs, + uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) : + TownsPC98_MusicChannelSSG(driver, regOffs, flgs, num, key, prt, id) {} + ~TownsPC98_SfxChannel() {} + + void loadData(uint8 *data); + void reset(); +}; + +#ifndef DISABLE_PC98_RHYTHM_CHANNEL +class TownsPC98_MusicChannelPCM : public TownsPC98_MusicChannel { +public: + TownsPC98_MusicChannelPCM(TownsPC98_AudioDriver *driver, uint8 regOffs, + uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id); + ~TownsPC98_MusicChannelPCM() {} + void init(); + + void loadData(uint8 *data); + void processEvents(); + bool processControlEvent(uint8 cmd); + +private: + bool control_f1_prcStart(uint8 para); + bool control_ff_endOfTrack(uint8 para); + + typedef bool (TownsPC98_MusicChannelPCM::*ControlEventFunc)(uint8 para); + const ControlEventFunc *controlEvents; +}; +#endif + +TownsPC98_MusicChannel::TownsPC98_MusicChannel(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, + uint8 key, uint8 prt, uint8 id) : _drv(driver), _regOffset(regOffs), _flags(flgs), _chanNum(num), _keyNum(key), + _part(prt), _idFlag(id), controlEvents(0) { + + _ticksLeft = _algorithm = _instr = _totalLevel = _frqBlockMSB = _keyOffTime = 0; + _ssgStartLvl = _ssgTl = _ssgStep = _ssgTicksLeft = _ssgTargetLvl = _block = 0; + _vbrInitDelayHi = _vbrInitDelayLo = _vbrDuration = _vbrCurDelay = _vbrDurLeft = 0; + _frqLSB = 0; + _hold = false; + _dataPtr = 0; + _vbrModInitVal = _vbrModCurVal = 0; + _frequency = 0; +} + +TownsPC98_MusicChannel::~TownsPC98_MusicChannel() { +} + +void TownsPC98_MusicChannel::init() { +#define Control(x) &TownsPC98_MusicChannel::control_##x + static const ControlEventFunc ctrlEvents[] = { + Control(f0_setPatch), + Control(f1_presetOutputLevel), + Control(f2_setKeyOffTime), + Control(f3_setFreqLSB), + Control(f4_setOutputLevel), + Control(f5_setTempo), + Control(f6_repeatSection), + Control(f7_setupVibrato), + Control(f8_toggleVibrato), + Control(dummy), + Control(fa_writeReg), + Control(fb_incOutLevel), + Control(fc_decOutLevel), + Control(fd_jump), + Control(dummy), + Control(ff_endOfTrack) + }; +#undef Control + + controlEvents = ctrlEvents; +} + +void TownsPC98_MusicChannel::keyOff() { + // all operators off + uint8 value = _keyNum & 0x0f; + if (_part) + value |= 4; + uint8 regAddress = 0x28; + _drv->writeReg(0, regAddress, value); + _flags |= CHS_KEYOFF; +} + +void TownsPC98_MusicChannel::keyOn() { + // all operators on + uint8 value = _keyNum | 0xf0; + if (_part) + value |= 4; + uint8 regAddress = 0x28; + _drv->writeReg(0, regAddress, value); +} + +void TownsPC98_MusicChannel::loadData(uint8 *data) { + _flags = (_flags & ~CHS_EOT) | CHS_ALLOFF; + _ticksLeft = 1; + _dataPtr = data; + _totalLevel = 0x7F; + + uint8 *tmp = _dataPtr; + for (bool loop = true; loop;) { + uint8 cmd = *tmp++; + if (cmd < 0xf0) { + tmp++; + } else if (cmd == 0xff) { + if (READ_LE_UINT16(tmp)) { + _drv->_looping |= _idFlag; + tmp += _drv->_opnFxCmdLen[cmd - 240]; + } else + loop = false; + } else if (cmd == 0xf6) { + // reset repeat section countdown + tmp[0] = tmp[1]; + tmp += 4; + } else { + tmp += _drv->_opnFxCmdLen[cmd - 240]; + } + } +} + +void TownsPC98_MusicChannel::processEvents() { + if (_flags & CHS_EOT) + return; + + if (!_hold && _ticksLeft == _keyOffTime) + keyOff(); + + if (--_ticksLeft) + return; + + if (!_hold) + keyOff(); + + uint8 cmd = 0; + bool loop = true; + + while (loop) { + cmd = *_dataPtr++; + if (cmd < 0xf0) + loop = false; + else if (!processControlEvent(cmd)) + return; + } + + uint8 para = *_dataPtr++; + + if (cmd == 0x80) { + keyOff(); + _hold = false; + } else { + keyOn(); + + if (_hold == false || cmd != _frqBlockMSB) + _flags |= CHS_RECALCFREQ; + + _hold = (para & 0x80) ? true : false; + _frqBlockMSB = cmd; + } + + _ticksLeft = para & 0x7f; +} + +void TownsPC98_MusicChannel::processFrequency() { + if (_flags & CHS_RECALCFREQ) { + + _frequency = (READ_LE_UINT16(&_drv->_opnFreqTable[(_frqBlockMSB & 0x0f) << 1]) + _frqLSB) | (((_frqBlockMSB & 0x70) >> 1) << 8); + + _drv->writeReg(_part, _regOffset + 0xa4, (_frequency >> 8)); + _drv->writeReg(_part, _regOffset + 0xa0, (_frequency & 0xff)); + + setupVibrato(); + } + + if (!(_flags & CHS_VBROFF)) { + if (!processVibrato()) + return; + + _drv->writeReg(_part, _regOffset + 0xa4, (_frequency >> 8)); + _drv->writeReg(_part, _regOffset + 0xa0, (_frequency & 0xff)); + } +} + +void TownsPC98_MusicChannel::setupVibrato() { + _vbrCurDelay = _vbrInitDelayHi; + if (_flags & CHS_KEYOFF) { + _vbrModCurVal = _vbrModInitVal; + _vbrCurDelay += _vbrInitDelayLo; + } + _vbrDurLeft = (_vbrDuration >> 1); + _flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ); +} + +bool TownsPC98_MusicChannel::processVibrato() { + if (--_vbrCurDelay) + return false; + + _vbrCurDelay = _vbrInitDelayHi; + _frequency += _vbrModCurVal; + + if (!--_vbrDurLeft) { + _vbrDurLeft = _vbrDuration; + _vbrModCurVal = -_vbrModCurVal; + } + + return true; +} + +bool TownsPC98_MusicChannel::processControlEvent(uint8 cmd) { + uint8 para = *_dataPtr++; + return (this->*controlEvents[cmd & 0x0f])(para); +} + +void TownsPC98_MusicChannel::setOutputLevel() { + uint8 outopr = _drv->_opnCarrier[_algorithm]; + uint8 reg = 0x40 + _regOffset; + + for (int i = 0; i < 4; i++) { + if (outopr & 1) + _drv->writeReg(_part, reg, _totalLevel); + outopr >>= 1; + reg += 4; + } +} + +void TownsPC98_MusicChannel::fadeStep() { + _totalLevel += 3; + if (_totalLevel > 0x7f) + _totalLevel = 0x7f; + setOutputLevel(); +} + +void TownsPC98_MusicChannel::reset() { + _hold = false; + _keyOffTime = 0; + _ticksLeft = 1; + + _flags = (_flags & ~CHS_EOT) | CHS_ALLOFF; + + _totalLevel = 0; + _algorithm = 0; + _flags = CHS_EOT; + _algorithm = 0; + + _block = 0; + _frequency = 0; + _frqBlockMSB = 0; + _frqLSB = 0; + + _ssgTl = 0; + _ssgStartLvl = 0; + _ssgTargetLvl = 0; + _ssgStep = 0; + _ssgTicksLeft = 0; + + _vbrInitDelayHi = 0; + _vbrInitDelayLo = 0; + _vbrModInitVal = 0; + _vbrDuration = 0; + _vbrCurDelay = 0; + _vbrModCurVal = 0; + _vbrDurLeft = 0; +} + +bool TownsPC98_MusicChannel::control_f0_setPatch(uint8 para) { + _instr = para; + uint8 reg = _regOffset + 0x80; + + for (int i = 0; i < 4; i++) { + // set release rate for each operator + _drv->writeReg(_part, reg, 0x0f); + reg += 4; + } + + const uint8 *tptr = _drv->_patches + ((uint32)_instr << 5); + reg = _regOffset + 0x30; + + // write registers 0x30 to 0x8f + for (int i = 0; i < 6; i++) { + _drv->writeReg(_part, reg, tptr[0]); + reg += 4; + _drv->writeReg(_part, reg, tptr[2]); + reg += 4; + _drv->writeReg(_part, reg, tptr[1]); + reg += 4; + _drv->writeReg(_part, reg, tptr[3]); + reg += 4; + tptr += 4; + } + + reg = _regOffset + 0xB0; + _algorithm = tptr[0] & 7; + // set feedback and algorithm + _drv->writeReg(_part, reg, tptr[0]); + + setOutputLevel(); + return true; +} + +bool TownsPC98_MusicChannel::control_f1_presetOutputLevel(uint8 para) { + if (_drv->_fading) + return true; + + _totalLevel = _drv->_opnLvlPresets[para]; + setOutputLevel(); + return true; +} + +bool TownsPC98_MusicChannel::control_f2_setKeyOffTime(uint8 para) { + _keyOffTime = para; + return true; +} + +bool TownsPC98_MusicChannel::control_f3_setFreqLSB(uint8 para) { + _frqLSB = (int8) para; + return true; +} + +bool TownsPC98_MusicChannel::control_f4_setOutputLevel(uint8 para) { + if (_drv->_fading) + return true; + + _totalLevel = para; + setOutputLevel(); + return true; +} + +bool TownsPC98_MusicChannel::control_f5_setTempo(uint8 para) { + _drv->setMusicTempo(para); + return true; +} + +bool TownsPC98_MusicChannel::control_f6_repeatSection(uint8 para) { + _dataPtr--; + _dataPtr[0]--; + + if (*_dataPtr) { + // repeat section until counter has reached zero + _dataPtr = _drv->_trackPtr + READ_LE_UINT16(_dataPtr + 2); + } else { + // reset counter, advance to next section + _dataPtr[0] = _dataPtr[1]; + _dataPtr += 4; + } + return true; +} + +bool TownsPC98_MusicChannel::control_f7_setupVibrato(uint8 para) { + _vbrInitDelayHi = _dataPtr[0]; + _vbrInitDelayLo = para; + _vbrModInitVal = (int16) READ_LE_UINT16(_dataPtr + 1); + _vbrDuration = _dataPtr[3]; + _dataPtr += 4; + _flags = (_flags & ~CHS_VBROFF) | CHS_KEYOFF | CHS_RECALCFREQ; + return true; +} + +bool TownsPC98_MusicChannel::control_f8_toggleVibrato(uint8 para) { + if (para == 0x10) { + if (*_dataPtr++) { + _flags = (_flags & ~CHS_VBROFF) | CHS_KEYOFF; + } else { + _flags |= CHS_VBROFF; + } + } else { + /* NOT IMPLEMENTED + uint8 skipChannels = para / 36; + uint8 entry = para % 36; + TownsPC98_AudioDriver::TownsPC98_MusicChannel *t = &chan[skipChannels]; + + t->unnamedEntries[entry] = *_dataPtr++;*/ + } + return true; +} + +bool TownsPC98_MusicChannel::control_fa_writeReg(uint8 para) { + _drv->writeReg(_part, para, *_dataPtr++); + return true; +} + +bool TownsPC98_MusicChannel::control_fb_incOutLevel(uint8 para) { + _dataPtr--; + if (_drv->_fading) + return true; + + uint8 val = (_totalLevel + 3); + if (val > 0x7f) + val = 0x7f; + + _totalLevel = val; + setOutputLevel(); + return true; +} + +bool TownsPC98_MusicChannel::control_fc_decOutLevel(uint8 para) { + _dataPtr--; + if (_drv->_fading) + return true; + + int8 val = (int8)(_totalLevel - 3); + if (val < 0) + val = 0; + + _totalLevel = (uint8) val; + setOutputLevel(); + return true; +} + +bool TownsPC98_MusicChannel::control_fd_jump(uint8 para) { + uint8 *tmp = _drv->_trackPtr + READ_LE_UINT16(_dataPtr - 1); + _dataPtr = (tmp[1] == 1) ? tmp : (_dataPtr + 1); + return true; +} + +bool TownsPC98_MusicChannel::control_dummy(uint8 para) { + _dataPtr--; + return true; +} + +bool TownsPC98_MusicChannel::control_ff_endOfTrack(uint8 para) { + uint16 val = READ_LE_UINT16(--_dataPtr); + if (val) { + // loop + _dataPtr = _drv->_trackPtr + val; + return true; + } else { + // quit parsing for active channel + --_dataPtr; + _flags |= CHS_EOT; + _drv->_finishedChannelsFlag |= _idFlag; + keyOff(); + return false; + } +} + +TownsPC98_MusicChannelSSG::TownsPC98_MusicChannelSSG(TownsPC98_AudioDriver *driver, uint8 regOffs, + uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) : + TownsPC98_MusicChannel(driver, regOffs, flgs, num, key, prt, id), controlEvents(0) { +} + +void TownsPC98_MusicChannelSSG::init() { + _algorithm = 0x80; + +#define Control(x) &TownsPC98_MusicChannelSSG::control_##x + static const ControlEventFunc ctrlEventsSSG[] = { + Control(f0_setPatch), + Control(f1_setTotalLevel), + Control(f2_setKeyOffTime), + Control(f3_setFreqLSB), + Control(f4_setAlgorithm), + Control(f5_setTempo), + Control(f6_repeatSection), + Control(f7_setupVibrato), + Control(f8_toggleVibrato), + Control(f9_loadCustomPatch), + Control(fa_writeReg), + Control(fb_incOutLevel), + Control(fc_decOutLevel), + Control(fd_jump), + Control(dummy), + Control(ff_endOfTrack) + }; +#undef Control + + controlEvents = ctrlEventsSSG; +} + +void TownsPC98_MusicChannelSSG::processEvents() { + if (_flags & CHS_EOT) + return; + + _drv->toggleRegProtection(_flags & CHS_PROTECT ? true : false); + + if (!_hold && _ticksLeft == _keyOffTime) + nextShape(); + + if (!--_ticksLeft) { + + uint8 cmd = 0; + bool loop = true; + + while (loop) { + cmd = *_dataPtr++; + if (cmd < 0xf0) + loop = false; + else if (!processControlEvent(cmd)) + return; + } + + uint8 para = *_dataPtr++; + + if (cmd == 0x80) { + nextShape(); + _hold = false; + } else { + if (!_hold) { + _instr &= 0xf0; + _ssgStep = _drv->_ssgPatches[_instr]; + _ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f; + _ssgTargetLvl = _drv->_ssgPatches[_instr + 2]; + _ssgStartLvl = _drv->_ssgPatches[_instr + 3]; + _flags = (_flags & ~CHS_SSGOFF) | CHS_KEYOFF; + } + + keyOn(); + + if (_hold == false || cmd != _frqBlockMSB) + _flags |= CHS_RECALCFREQ; + + _hold = (para & 0x80) ? true : false; + _frqBlockMSB = cmd; + } + + _ticksLeft = para & 0x7f; + } + + if (!(_flags & CHS_SSGOFF)) { + if (--_ssgTicksLeft) { + if (!_drv->_fading) + setOutputLevel(_ssgStartLvl); + return; + } + + _ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f; + + if (_drv->_ssgPatches[_instr + 1] & 0x80) { + uint8 t = _ssgStartLvl - _ssgStep; + + if (_ssgStep <= _ssgStartLvl && _ssgTargetLvl < t) { + if (!_drv->_fading) + setOutputLevel(t); + return; + } + } else { + int t = _ssgStartLvl + _ssgStep; + uint8 p = (uint8)(t & 0xff); + + if (t < 256 && _ssgTargetLvl > p) { + if (!_drv->_fading) + setOutputLevel(p); + return; + } + } + + setOutputLevel(_ssgTargetLvl); + if (_ssgStartLvl && !(_instr & 8)) { + _instr += 4; + _ssgStep = _drv->_ssgPatches[_instr]; + _ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f; + _ssgTargetLvl = _drv->_ssgPatches[_instr + 2]; + } else { + _flags |= CHS_SSGOFF; + setOutputLevel(0); + } + } +} + +void TownsPC98_MusicChannelSSG::processFrequency() { + if (_algorithm & 0x40) + return; + + if (_flags & CHS_RECALCFREQ) { + _block = _frqBlockMSB >> 4; + _frequency = READ_LE_UINT16(&_drv->_opnFreqTableSSG[(_frqBlockMSB & 0x0f) << 1]) + _frqLSB; + + uint16 f = _frequency >> _block; + _drv->writeReg(_part, _regOffset << 1, f & 0xff); + _drv->writeReg(_part, (_regOffset << 1) + 1, f >> 8); + + setupVibrato(); + } + + if (!(_flags & (CHS_EOT | CHS_VBROFF | CHS_SSGOFF))) { + if (!processVibrato()) + return; + + uint16 f = _frequency >> _block; + _drv->writeReg(_part, _regOffset << 1, f & 0xff); + _drv->writeReg(_part, (_regOffset << 1) + 1, f >> 8); + } +} + +bool TownsPC98_MusicChannelSSG::processControlEvent(uint8 cmd) { + uint8 para = *_dataPtr++; + return (this->*controlEvents[cmd & 0x0f])(para); +} + +void TownsPC98_MusicChannelSSG::nextShape() { + _instr = (_instr & 0xf0) + 0x0c; + _ssgStep = _drv->_ssgPatches[_instr]; + _ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f; + _ssgTargetLvl = _drv->_ssgPatches[_instr + 2]; +} + +void TownsPC98_MusicChannelSSG::keyOn() { + uint8 c = 0x7b; + uint8 t = (_algorithm & 0xC0) << 1; + if (_algorithm & 0x80) + t |= 4; + + c = (c << (_regOffset + 1)) | (c >> (7 - _regOffset)); + t = (t << (_regOffset + 1)) | (t >> (7 - _regOffset)); + + if (!(_algorithm & 0x80)) + _drv->writeReg(_part, 6, _algorithm & 0x7f); + + uint8 e = (_drv->readSSGStatus() & c) | t; + _drv->writeReg(_part, 7, e); +} + +void TownsPC98_MusicChannelSSG::protect() { + _flags |= CHS_PROTECT; +} + +void TownsPC98_MusicChannelSSG::restore() { + _flags &= ~CHS_PROTECT; + keyOn(); + _drv->writeReg(_part, 8 + _regOffset, _ssgTl); + uint16 f = _frequency >> _block; + _drv->writeReg(_part, _regOffset << 1, f & 0xff); + _drv->writeReg(_part, (_regOffset << 1) + 1, f >> 8); +} + +void TownsPC98_MusicChannelSSG::loadData(uint8 *data) { + _drv->toggleRegProtection(_flags & CHS_PROTECT ? true : false); + TownsPC98_MusicChannel::loadData(data); + setOutputLevel(0); + _algorithm = 0x80; +} + +void TownsPC98_MusicChannelSSG::setOutputLevel(uint8 lvl) { + _ssgStartLvl = lvl; + uint16 newTl = (((uint16)_totalLevel + 1) * (uint16)lvl) >> 8; + if (newTl == _ssgTl) + return; + _ssgTl = newTl; + _drv->writeReg(_part, 8 + _regOffset, _ssgTl); +} + +void TownsPC98_MusicChannelSSG::reset() { + TownsPC98_MusicChannel::reset(); + + // Unlike the original we restore the default patch data. This fixes a bug + // where certain sound effects would bring each other out of tune (e.g. the + // dragon's fire in Darm's house in Kyra 1 would sound different each time + // you triggered another sfx by dropping an item etc.) + uint8 i = (10 + _regOffset) << 4; + const uint8 *src = &_drv->_drvTables[156]; + _drv->_ssgPatches[i] = src[i]; + _drv->_ssgPatches[i + 3] = src[i + 3]; + _drv->_ssgPatches[i + 4] = src[i + 4]; + _drv->_ssgPatches[i + 6] = src[i + 6]; + _drv->_ssgPatches[i + 8] = src[i + 8]; + _drv->_ssgPatches[i + 12] = src[i + 12]; +} + +void TownsPC98_MusicChannelSSG::fadeStep() { + _totalLevel--; + if ((int8)_totalLevel < 0) + _totalLevel = 0; + setOutputLevel(_ssgStartLvl); +} + +bool TownsPC98_MusicChannelSSG::control_f0_setPatch(uint8 para) { + _instr = para << 4; + para = (para >> 3) & 0x1e; + if (para) + return control_f4_setAlgorithm(para | 0x40); + return true; +} + +bool TownsPC98_MusicChannelSSG::control_f1_setTotalLevel(uint8 para) { + if (!_drv->_fading) + _totalLevel = para; + return true; +} + +bool TownsPC98_MusicChannelSSG::control_f4_setAlgorithm(uint8 para) { + _algorithm = para; + return true; +} + +bool TownsPC98_MusicChannelSSG::control_f9_loadCustomPatch(uint8 para) { + _instr = (_drv->_sfxOffs + 10 + _regOffset) << 4; + _drv->_ssgPatches[_instr] = *_dataPtr++; + _drv->_ssgPatches[_instr + 3] = para; + _drv->_ssgPatches[_instr + 4] = *_dataPtr++; + _drv->_ssgPatches[_instr + 6] = *_dataPtr++; + _drv->_ssgPatches[_instr + 8] = *_dataPtr++; + _drv->_ssgPatches[_instr + 12] = *_dataPtr++; + return true; +} + +bool TownsPC98_MusicChannelSSG::control_fb_incOutLevel(uint8 para) { + _dataPtr--; + if (_drv->_fading) + return true; + + _totalLevel--; + if ((int8)_totalLevel < 0) + _totalLevel = 0; + + return true; +} + +bool TownsPC98_MusicChannelSSG::control_fc_decOutLevel(uint8 para) { + _dataPtr--; + if (_drv->_fading) + return true; + + if (_totalLevel + 1 < 0x10) + _totalLevel++; + + return true; +} + +bool TownsPC98_MusicChannelSSG::control_ff_endOfTrack(uint8 para) { + if (!_drv->_sfxOffs) { + uint16 val = READ_LE_UINT16(--_dataPtr); + if (val) { + // loop + _dataPtr = _drv->_trackPtr + val; + return true; + } else { + // stop parsing + if (!_drv->_fading) + setOutputLevel(0); + --_dataPtr; + _flags |= CHS_EOT; + _drv->_finishedSSGFlag |= _idFlag; + } + } else { + // end of sfx track - restore ssg music channel + _flags |= CHS_EOT; + _drv->_finishedSfxFlag |= _idFlag; + _drv->_ssgChannels[_chanNum]->restore(); + } + + return false; +} + +void TownsPC98_SfxChannel::loadData(uint8 *data) { + _flags = CHS_ALLOFF; + _ticksLeft = 1; + _dataPtr = data; + _ssgTl = 0xff; + _algorithm = 0x80; + + uint8 *tmp = _dataPtr; + for (bool loop = true; loop;) { + uint8 cmd = *tmp++; + if (cmd < 0xf0) { + tmp++; + } else if (cmd == 0xff) { + loop = false; + } else if (cmd == 0xf6) { + // reset repeat section countdown + tmp[0] = tmp[1]; + tmp += 4; + } else { + tmp += _drv->_opnFxCmdLen[cmd - 240]; + } + } +} + +void TownsPC98_SfxChannel::reset() { + TownsPC98_MusicChannel::reset(); + + // Unlike the original we restore the default patch data. This fixes a bug + // where certain sound effects would bring each other out of tune (e.g. the + // dragon's fire in Darm's house in Kyra 1 would sound different each time + // you triggered another sfx by dropping an item etc.) + uint8 i = (13 + _regOffset) << 4; + const uint8 *src = &_drv->_drvTables[156]; + _drv->_ssgPatches[i] = src[i]; + _drv->_ssgPatches[i + 3] = src[i + 3]; + _drv->_ssgPatches[i + 4] = src[i + 4]; + _drv->_ssgPatches[i + 6] = src[i + 6]; + _drv->_ssgPatches[i + 8] = src[i + 8]; + _drv->_ssgPatches[i + 12] = src[i + 12]; +} + +#ifndef DISABLE_PC98_RHYTHM_CHANNEL +TownsPC98_MusicChannelPCM::TownsPC98_MusicChannelPCM(TownsPC98_AudioDriver *driver, uint8 regOffs, + uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) : + TownsPC98_MusicChannel(driver, regOffs, flgs, num, key, prt, id), controlEvents(0) { +} + +void TownsPC98_MusicChannelPCM::init() { + _algorithm = 0x80; + +#define Control(x) &TownsPC98_MusicChannelPCM::control_##x + static const ControlEventFunc ctrlEventsPCM[] = { + Control(dummy), + Control(f1_prcStart), + Control(dummy), + Control(dummy), + Control(dummy), + Control(dummy), + Control(f6_repeatSection), + Control(dummy), + Control(dummy), + Control(dummy), + Control(fa_writeReg), + Control(dummy), + Control(dummy), + Control(dummy), + Control(dummy), + Control(ff_endOfTrack) + }; +#undef Control + + controlEvents = ctrlEventsPCM; +} + +void TownsPC98_MusicChannelPCM::loadData(uint8 *data) { + _flags = (_flags & ~CHS_EOT) | CHS_ALLOFF; + _ticksLeft = 1; + _dataPtr = data; + _totalLevel = 0x7F; +} + +void TownsPC98_MusicChannelPCM::processEvents() { + if (_flags & CHS_EOT) + return; + + if (--_ticksLeft) + return; + + uint8 cmd = 0; + bool loop = true; + + while (loop) { + cmd = *_dataPtr++; + if (cmd == 0x80) { + loop = false; + } else if (cmd < 0xf0) { + _drv->writeReg(_part, 0x10, cmd); + } else if (!processControlEvent(cmd)) { + return; + } + } + + _ticksLeft = *_dataPtr++; +} + +bool TownsPC98_MusicChannelPCM::processControlEvent(uint8 cmd) { + uint8 para = *_dataPtr++; + return (this->*controlEvents[cmd & 0x0f])(para); +} + +bool TownsPC98_MusicChannelPCM::control_f1_prcStart(uint8 para) { + _totalLevel = para; + _drv->writeReg(_part, 0x11, para); + return true; +} + +bool TownsPC98_MusicChannelPCM::control_ff_endOfTrack(uint8 para) { + uint16 val = READ_LE_UINT16(--_dataPtr); + if (val) { + // loop + _dataPtr = _drv->_trackPtr + val; + return true; + } else { + // quit parsing for active channel + --_dataPtr; + _flags |= CHS_EOT; + _drv->_finishedRhythmFlag |= _idFlag; + return false; + } +} +#endif // DISABLE_PC98_RHYTHM_CHANNEL + +TownsPC98_AudioDriver::TownsPC98_AudioDriver(Audio::Mixer *mixer, EmuType type) : TownsPC98_FmSynth(mixer, type), + _channels(0), _ssgChannels(0), _sfxChannels(0), +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + _rhythmChannel(0), +#endif + _trackPtr(0), _sfxData(0), _sfxOffs(0), _ssgPatches(0), + _patches(0), _sfxBuffer(0), _musicBuffer(0), + + _opnCarrier(_drvTables + 76), _opnFreqTable(_drvTables + 108), _opnFreqTableSSG(_drvTables + 132), + _opnFxCmdLen(_drvTables + 36), _opnLvlPresets(_drvTables + (type == kTypeTowns ? 52 : 84)), + + _updateChannelsFlag(type == kType26 ? 0x07 : 0x3F), _finishedChannelsFlag(0), + _updateSSGFlag(type == kTypeTowns ? 0x00 : 0x07), _finishedSSGFlag(0), + _updateRhythmFlag(type == kType86 ? +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + 0x01 +#else + 0x00 +#endif + : 0x00), _finishedRhythmFlag(0), + _updateSfxFlag(0), _finishedSfxFlag(0), + + _musicTickCounter(0), + + _musicVolume(255), _sfxVolume(255), + + _musicPlaying(false), _sfxPlaying(false), _fading(false), _looping(0), _ready(false) { + + _sfxOffsets[0] = _sfxOffsets[1] = 0; +} + +TownsPC98_AudioDriver::~TownsPC98_AudioDriver() { + _ready = false; + deinit(); + + if (_channels) { + for (int i = 0; i < _numChan; i++) + delete _channels[i]; + delete[] _channels; + } + + if (_ssgChannels) { + for (int i = 0; i < _numSSG; i++) + delete _ssgChannels[i]; + delete[] _ssgChannels; + } + + if (_sfxChannels) { + for (int i = 0; i < 2; i++) + delete _sfxChannels[i]; + delete[] _sfxChannels; + } +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + delete _rhythmChannel; +#endif + + delete[] _ssgPatches; +} + +bool TownsPC98_AudioDriver::init() { + if (_ready) { + reset(); + return true; + } + + TownsPC98_FmSynth::init(); + + setVolumeChannelMasks(-1, 0); + + _channels = new TownsPC98_MusicChannel *[_numChan]; + for (int i = 0; i < _numChan; i++) { + int ii = i * 6; + _channels[i] = new TownsPC98_MusicChannel(this, _drvTables[ii], _drvTables[ii + 1], + _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]); + _channels[i]->init(); + } + + if (_numSSG) { + _ssgPatches = new uint8[256]; + memcpy(_ssgPatches, _drvTables + 156, 256); + + _ssgChannels = new TownsPC98_MusicChannelSSG *[_numSSG]; + for (int i = 0; i < _numSSG; i++) { + int ii = i * 6; + _ssgChannels[i] = new TownsPC98_MusicChannelSSG(this, _drvTables[ii], _drvTables[ii + 1], + _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]); + _ssgChannels[i]->init(); + } + + _sfxChannels = new TownsPC98_SfxChannel *[2]; + for (int i = 0; i < 2; i++) { + int ii = (i + 1) * 6; + _sfxChannels[i] = new TownsPC98_SfxChannel(this, _drvTables[ii], _drvTables[ii + 1], + _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]); + _sfxChannels[i]->init(); + } + } + +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + if (_hasPercussion) { + _rhythmChannel = new TownsPC98_MusicChannelPCM(this, 0, 0, 0, 0, 0, 1); + _rhythmChannel->init(); + } +#endif + + setMusicTempo(84); + setSfxTempo(654); + + _ready = true; + + return true; +} + +void TownsPC98_AudioDriver::loadMusicData(uint8 *data, bool loadPaused) { + if (!_ready) { + warning("TownsPC98_AudioDriver: Driver must be initialized before loading data"); + return; + } + + if (!data) { + warning("TownsPC98_AudioDriver: Invalid music file data"); + return; + } + + reset(); + + Common::StackLock lock(_mutex); + uint8 *src_a = _trackPtr = _musicBuffer = data; + + for (uint8 i = 0; i < 3; i++) { + _channels[i]->loadData(data + READ_LE_UINT16(src_a)); + src_a += 2; + } + + for (int i = 0; i < _numSSG; i++) { + _ssgChannels[i]->loadData(data + READ_LE_UINT16(src_a)); + src_a += 2; + } + + for (uint8 i = 3; i < _numChan; i++) { + _channels[i]->loadData(data + READ_LE_UINT16(src_a)); + src_a += 2; + } + + if (_hasPercussion) { +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + _rhythmChannel->loadData(data + READ_LE_UINT16(src_a)); +#endif + src_a += 2; + } + + toggleRegProtection(false); + + _patches = src_a + 4; + _finishedChannelsFlag = _finishedSSGFlag = _finishedRhythmFlag = 0; + + _musicPlaying = (loadPaused ? false : true); +} + +void TownsPC98_AudioDriver::loadSoundEffectData(uint8 *data, uint8 trackNum) { + if (!_ready) { + warning("TownsPC98_AudioDriver: Driver must be initialized before loading data"); + return; + } + + if (!_sfxChannels) { + warning("TownsPC98_AudioDriver: Sound effects not supported by this configuration"); + return; + } + + if (!data) { + warning("TownsPC98_AudioDriver: Invalid sound effects file data"); + return; + } + + Common::StackLock lock(_mutex); + _sfxData = _sfxBuffer = data; + _sfxOffsets[0] = READ_LE_UINT16(&_sfxData[(trackNum << 2)]); + _sfxOffsets[1] = READ_LE_UINT16(&_sfxData[(trackNum << 2) + 2]); + _sfxPlaying = true; + _finishedSfxFlag = 0; +} + +void TownsPC98_AudioDriver::reset() { + Common::StackLock lock(_mutex); + + _musicPlaying = false; + _sfxPlaying = false; + _fading = false; + _looping = 0; + _musicTickCounter = 0; + _sfxData = 0; + + TownsPC98_FmSynth::reset(); + + for (int i = 0; i < _numChan; i++) + _channels[i]->reset(); + for (int i = 0; i < _numSSG; i++) + _ssgChannels[i]->reset(); + + if (_numSSG) { + for (int i = 0; i < 2; i++) + _sfxChannels[i]->reset(); + + memcpy(_ssgPatches, _drvTables + 156, 256); + } + +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + if (_rhythmChannel) + _rhythmChannel->reset(); +#endif +} + +void TownsPC98_AudioDriver::fadeStep() { + if (!_musicPlaying) + return; + + Common::StackLock lock(_mutex); + for (int j = 0; j < _numChan; j++) { + if (_updateChannelsFlag & _channels[j]->_idFlag) + _channels[j]->fadeStep(); + } + + for (int j = 0; j < _numSSG; j++) { + if (_updateSSGFlag & _ssgChannels[j]->_idFlag) + _ssgChannels[j]->fadeStep(); + } + + if (!_fading) { + _fading = 19; +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + if (_hasPercussion) { + if (_updateRhythmFlag & _rhythmChannel->_idFlag) + _rhythmChannel->reset(); + } +#endif + } else { + if (!--_fading) + reset(); + } +} + +void TownsPC98_AudioDriver::timerCallbackB() { + _sfxOffs = 0; + + if (_musicPlaying) { + _musicTickCounter++; + + for (int i = 0; i < _numChan; i++) { + if (_updateChannelsFlag & _channels[i]->_idFlag) { + _channels[i]->processEvents(); + _channels[i]->processFrequency(); + } + } + + for (int i = 0; i < _numSSG; i++) { + if (_updateSSGFlag & _ssgChannels[i]->_idFlag) { + _ssgChannels[i]->processEvents(); + _ssgChannels[i]->processFrequency(); + } + } + +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + if (_hasPercussion) + if (_updateRhythmFlag & _rhythmChannel->_idFlag) + _rhythmChannel->processEvents(); +#endif + } + + toggleRegProtection(false); + + if (_finishedChannelsFlag == _updateChannelsFlag && _finishedSSGFlag == _updateSSGFlag && _finishedRhythmFlag == _updateRhythmFlag) + _musicPlaying = false; +} + +void TownsPC98_AudioDriver::timerCallbackA() { + if (_sfxChannels && _sfxPlaying) { + if (_sfxData) + startSoundEffect(); + + _sfxOffs = 3; + _trackPtr = _sfxBuffer; + + for (int i = 0; i < 2; i++) { + if (_updateSfxFlag & _sfxChannels[i]->_idFlag) { + _sfxChannels[i]->processEvents(); + _sfxChannels[i]->processFrequency(); + } + } + + _trackPtr = _musicBuffer; + } + + if (_updateSfxFlag && _finishedSfxFlag == _updateSfxFlag) { + _sfxPlaying = false; + _updateSfxFlag = 0; + setVolumeChannelMasks(-1, 0); + } +} + +void TownsPC98_AudioDriver::setMusicTempo(uint8 tempo) { + writeReg(0, 0x26, tempo); + writeReg(0, 0x27, 0x33); +} + +void TownsPC98_AudioDriver::setSfxTempo(uint16 tempo) { + writeReg(0, 0x24, tempo & 0xff); + writeReg(0, 0x25, tempo >> 8); + writeReg(0, 0x27, 0x33); +} + +void TownsPC98_AudioDriver::startSoundEffect() { + int volFlags = 0; + + for (int i = 0; i < 2; i++) { + if (_sfxOffsets[i]) { + _ssgChannels[i + 1]->protect(); + _sfxChannels[i]->reset(); + _sfxChannels[i]->loadData(_sfxData + _sfxOffsets[i]); + _updateSfxFlag |= _sfxChannels[i]->_idFlag; + volFlags |= (_sfxChannels[i]->_idFlag << _numChan); + } else { + _ssgChannels[i + 1]->restore(); + _updateSfxFlag &= ~_sfxChannels[i]->_idFlag; + } + } + + setVolumeChannelMasks(~volFlags, volFlags); + _sfxData = 0; +} + +const uint8 TownsPC98_AudioDriver::_drvTables[] = { + // channel presets + 0x00, 0x80, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x80, 0x01, 0x01, 0x00, 0x02, + 0x02, 0x80, 0x02, 0x02, 0x00, 0x04, + 0x00, 0x80, 0x03, 0x04, 0x01, 0x08, + 0x01, 0x80, 0x04, 0x05, 0x01, 0x10, + 0x02, 0x80, 0x05, 0x06, 0x01, 0x20, + + // control event size + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x05, + 0x02, 0x06, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, + + // fmt level presets + 0x54, 0x50, 0x4C, 0x48, 0x44, 0x40, 0x3C, 0x38, + 0x34, 0x30, 0x2C, 0x28, 0x24, 0x20, 0x1C, 0x18, + 0x14, 0x10, 0x0C, 0x08, 0x04, 0x90, 0x90, 0x90, + + // carriers + 0x08, 0x08, 0x08, 0x08, 0x0C, 0x0E, 0x0E, 0x0F, + + // pc98 level presets + 0x40, 0x3B, 0x38, 0x34, 0x30, 0x2A, 0x28, 0x25, + 0x22, 0x20, 0x1D, 0x1A, 0x18, 0x15, 0x12, 0x10, + 0x0D, 0x0A, 0x08, 0x05, 0x02, 0x90, 0x90, 0x90, + + // frequencies + 0x6A, 0x02, 0x8F, 0x02, 0xB6, 0x02, 0xDF, 0x02, + 0x0B, 0x03, 0x39, 0x03, 0x6A, 0x03, 0x9E, 0x03, + 0xD5, 0x03, 0x10, 0x04, 0x4E, 0x04, 0x8F, 0x04, + + // ssg frequencies + 0xE8, 0x0E, 0x12, 0x0E, 0x48, 0x0D, 0x89, 0x0C, + 0xD5, 0x0B, 0x2B, 0x0B, 0x8A, 0x0A, 0xF3, 0x09, + 0x64, 0x09, 0xDD, 0x08, 0x5E, 0x08, 0xE6, 0x07, + + // ssg patch data + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00, + 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0x37, 0x81, 0xC8, 0x00, + 0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0x37, 0x81, 0xC8, 0x00, + 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00, + 0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00, + 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00, + 0x04, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00, + 0x0A, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0x01, 0x00, + 0xFF, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00, + 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0x00, + 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, + 0x64, 0x01, 0xFF, 0x64, 0xFF, 0x81, 0xFF, 0x00, + 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, + + 0x02, 0x01, 0xFF, 0x28, 0xFF, 0x81, 0xF0, 0x00, + 0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xC8, 0x00, + 0x01, 0x81, 0x00, 0x00, 0x28, 0x81, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0x78, 0x5F, 0x81, 0xA0, 0x00, + 0x05, 0x81, 0x00, 0x00, 0x28, 0x81, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00, + 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00, + 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00, + 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00 +}; + +#undef EUPHONY_FADEOUT_TICKS + diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_driver.h b/audio/softsynth/fmtowns_pc98/towns_pc98_driver.h new file mode 100644 index 0000000000..1227e2626a --- /dev/null +++ b/audio/softsynth/fmtowns_pc98/towns_pc98_driver.h @@ -0,0 +1,135 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TOWNS_PC98_AUDIODRIVER_H +#define TOWNS_PC98_AUDIODRIVER_H + +#include "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h" + +class TownsPC98_MusicChannel; +class TownsPC98_MusicChannelSSG; +class TownsPC98_SfxChannel; +#ifndef DISABLE_PC98_RHYTHM_CHANNEL +class TownsPC98_MusicChannelPCM; +#endif + +class TownsPC98_AudioDriver : public TownsPC98_FmSynth { +friend class TownsPC98_MusicChannel; +friend class TownsPC98_MusicChannelSSG; +friend class TownsPC98_SfxChannel; +#ifndef DISABLE_PC98_RHYTHM_CHANNEL +friend class TownsPC98_MusicChannelPCM; +#endif +public: + TownsPC98_AudioDriver(Audio::Mixer *mixer, EmuType type); + ~TownsPC98_AudioDriver(); + + void loadMusicData(uint8 *data, bool loadPaused = false); + void loadSoundEffectData(uint8 *data, uint8 trackNum); + bool init(); + void reset(); + + void fadeStep(); + + void pause() { + _musicPlaying = false; + } + void cont() { + _musicPlaying = true; + } + + void timerCallbackB(); + void timerCallbackA(); + + bool looping() { + return _looping == _updateChannelsFlag ? true : false; + } + bool musicPlaying() { + return _musicPlaying; + } + + void setMusicVolume(int volume) { + _musicVolume = volume; + setVolumeIntern(_musicVolume, _sfxVolume); + } + void setSoundEffectVolume(int volume) { + _sfxVolume = volume; + setVolumeIntern(_musicVolume, _sfxVolume); + } + +protected: + void startSoundEffect(); + + void setMusicTempo(uint8 tempo); + void setSfxTempo(uint16 tempo); + + TownsPC98_MusicChannel **_channels; + TownsPC98_MusicChannelSSG **_ssgChannels; + TownsPC98_SfxChannel **_sfxChannels; +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + TownsPC98_MusicChannelPCM *_rhythmChannel; +#endif + + const uint8 *_opnCarrier; + const uint8 *_opnFreqTable; + const uint8 *_opnFreqTableSSG; + const uint8 *_opnFxCmdLen; + const uint8 *_opnLvlPresets; + + uint8 *_musicBuffer; + uint8 *_sfxBuffer; + uint8 *_trackPtr; + uint8 *_patches; + uint8 *_ssgPatches; + + uint8 _updateChannelsFlag; + uint8 _updateSSGFlag; + uint8 _updateRhythmFlag; + uint8 _updateSfxFlag; + uint8 _finishedChannelsFlag; + uint8 _finishedSSGFlag; + uint8 _finishedRhythmFlag; + uint8 _finishedSfxFlag; + + bool _musicPlaying; + bool _sfxPlaying; + uint8 _fading; + uint8 _looping; + uint32 _musicTickCounter; + + int _sfxOffs; + uint8 *_sfxData; + uint16 _sfxOffsets[2]; + + uint16 _musicVolume; + uint16 _sfxVolume; + + static const uint8 _drvTables[]; + + bool _ready; +}; + +#endif + diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp new file mode 100644 index 0000000000..f84fd841a6 --- /dev/null +++ b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp @@ -0,0 +1,1548 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h" +#include "common/endian.h" + +class TownsPC98_FmSynthOperator { +public: + TownsPC98_FmSynthOperator(const uint32 timerbase, const uint32 rtt, const uint8 *rateTable, + const uint8 *shiftTable, const uint8 *attackDecayTable, const uint32 *frqTable, + const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable); + ~TownsPC98_FmSynthOperator() {} + + void keyOn(); + void keyOff(); + void frequency(int freq); + void updatePhaseIncrement(); + void recalculateRates(); + void generateOutput(int32 phasebuf, int32 *feedbuf, int32 &out); + + void feedbackLevel(int32 level) { + _feedbackLevel = level ? level + 6 : 0; + } + void detune(int value) { + _detn = &_detnTbl[value << 5]; + } + void multiple(uint32 value) { + _multiple = value ? (value << 1) : 1; + } + void attackRate(uint32 value) { + _specifiedAttackRate = value; + } + bool scaleRate(uint8 value); + void decayRate(uint32 value) { + _specifiedDecayRate = value; + recalculateRates(); + } + void sustainRate(uint32 value) { + _specifiedSustainRate = value; + recalculateRates(); + } + void sustainLevel(uint32 value) { + _sustainLevel = (value == 0x0f) ? 0x3e0 : value << 5; + } + void releaseRate(uint32 value) { + _specifiedReleaseRate = value; + recalculateRates(); + } + void totalLevel(uint32 value) { + _totalLevel = value << 3; + } + void ampModulation(bool enable) { + _ampMod = enable; + } + void reset(); + +protected: + EnvelopeState _state; + bool _playing; + uint32 _feedbackLevel; + uint32 _multiple; + uint32 _totalLevel; + uint8 _keyScale1; + uint8 _keyScale2; + uint32 _specifiedAttackRate; + uint32 _specifiedDecayRate; + uint32 _specifiedSustainRate; + uint32 _specifiedReleaseRate; + uint32 _tickCount; + uint32 _sustainLevel; + + bool _ampMod; + uint32 _frequency; + uint8 _kcode; + uint32 _phase; + uint32 _phaseIncrement; + const int32 *_detn; + + const uint8 *_rateTbl; + const uint8 *_rshiftTbl; + const uint8 *_adTbl; + const uint32 *_fTbl; + const uint32 *_sinTbl; + const int32 *_tLvlTbl; + const int32 *_detnTbl; + + const uint32 _tickLength; + uint32 _timer; + const uint32 _rtt; + int32 _currentLevel; + + struct EvpState { + uint8 rate; + uint8 shift; + } fs_a, fs_d, fs_s, fs_r; +}; + +TownsPC98_FmSynthOperator::TownsPC98_FmSynthOperator(const uint32 timerbase, const uint32 rtt, + const uint8 *rateTable, const uint8 *shiftTable, const uint8 *attackDecayTable, + const uint32 *frqTable, const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable) : + _rtt(rtt), _rateTbl(rateTable), _rshiftTbl(shiftTable), _adTbl(attackDecayTable), _fTbl(frqTable), + _sinTbl(sineTable), _tLvlTbl(tlevelOut), _detnTbl(detuneTable), _tickLength(timerbase * 2), + _specifiedAttackRate(0), _specifiedDecayRate(0), _specifiedReleaseRate(0), _specifiedSustainRate(0), + _phase(0), _state(kEnvReady), _playing(false), _timer(0), _keyScale1(0), + _keyScale2(0), _currentLevel(1023), _ampMod(false), _tickCount(0) { + + fs_a.rate = fs_a.shift = fs_d.rate = fs_d.shift = fs_s.rate = fs_s.shift = fs_r.rate = fs_r.shift = 0; + + reset(); +} + +void TownsPC98_FmSynthOperator::keyOn() { + if (_playing) + return; + + _playing = true; + _state = kEnvAttacking; + _phase = 0; +} + +void TownsPC98_FmSynthOperator::keyOff() { + if (!_playing) + return; + + _playing = false; + if (_state != kEnvReady) + _state = kEnvReleasing; +} + +void TownsPC98_FmSynthOperator::frequency(int freq) { + uint8 block = (freq >> 11); + uint16 pos = (freq & 0x7ff); + uint8 c = pos >> 7; + + _kcode = (block << 2) | ((c < 7) ? 0 : ((c > 8) ? 3 : c - 6)); + _frequency = _fTbl[pos << 1] >> (7 - block); +} + +void TownsPC98_FmSynthOperator::updatePhaseIncrement() { + _phaseIncrement = ((_frequency + _detn[_kcode]) * _multiple) >> 1; + uint8 keyscale = _kcode >> _keyScale1; + if (_keyScale2 != keyscale) { + _keyScale2 = keyscale; + recalculateRates(); + } +} + +void TownsPC98_FmSynthOperator::recalculateRates() { + int k = _keyScale2; + int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0; + fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136; + fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0; + + r = _specifiedDecayRate ? (_specifiedDecayRate << 1) + 0x20 : 0; + fs_d.rate = _rateTbl[r + k]; + fs_d.shift = _rshiftTbl[r + k]; + + r = _specifiedSustainRate ? (_specifiedSustainRate << 1) + 0x20 : 0; + fs_s.rate = _rateTbl[r + k]; + fs_s.shift = _rshiftTbl[r + k]; + + r = (_specifiedReleaseRate << 2) + 0x22; + fs_r.rate = _rateTbl[r + k]; + fs_r.shift = _rshiftTbl[r + k]; +} + +void TownsPC98_FmSynthOperator::generateOutput(int32 phasebuf, int32 *feed, int32 &out) { + if (_state == kEnvReady) + return; + + _timer += _tickLength; + while (_timer > _rtt) { + _timer -= _rtt; + ++_tickCount; + + int32 levelIncrement = 0; + uint32 targetTime = 0; + int32 targetLevel = 0; + EnvelopeState nextState = kEnvReady; + + switch (_state) { + case kEnvReady: + return; + case kEnvAttacking: + targetLevel = 0; + nextState = kEnvDecaying; + if ((_specifiedAttackRate << 1) + _keyScale2 < 64) { + targetTime = (1 << fs_a.shift) - 1; + levelIncrement = (~_currentLevel * _adTbl[fs_a.rate + ((_tickCount >> fs_a.shift) & 7)]) >> 4; + break; + } else { + _currentLevel = targetLevel; + _state = nextState; + } + // Fall through + case kEnvDecaying: + targetTime = (1 << fs_d.shift) - 1; + nextState = kEnvSustaining; + targetLevel = _sustainLevel; + levelIncrement = _adTbl[fs_d.rate + ((_tickCount >> fs_d.shift) & 7)]; + break; + case kEnvSustaining: + targetTime = (1 << fs_s.shift) - 1; + nextState = kEnvSustaining; + targetLevel = 1023; + levelIncrement = _adTbl[fs_s.rate + ((_tickCount >> fs_s.shift) & 7)]; + break; + case kEnvReleasing: + targetTime = (1 << fs_r.shift) - 1; + nextState = kEnvReady; + targetLevel = 1023; + levelIncrement = _adTbl[fs_r.rate + ((_tickCount >> fs_r.shift) & 7)]; + break; + } + + if (!(_tickCount & targetTime)) { + _currentLevel += levelIncrement; + if ((_state == kEnvAttacking && _currentLevel <= targetLevel) || (_state != kEnvAttacking && _currentLevel >= targetLevel)) { + if (_state != kEnvDecaying) + _currentLevel = targetLevel; + _state = nextState; + } + } + } + + uint32 lvlout = _totalLevel + (uint32) _currentLevel; + + + int32 outp = 0; + int32 *i = &outp, *o = &outp; + int phaseShift = 0; + + if (feed) { + o = &feed[0]; + i = &feed[1]; + phaseShift = _feedbackLevel ? ((*o + *i) << _feedbackLevel) : 0; + *o = *i; + } else { + phaseShift = phasebuf << 15; + } + + if (lvlout < 832) { + uint32 index = (lvlout << 3) + _sinTbl[(((int32)((_phase & 0xffff0000) + + phaseShift)) >> 16) & 0x3ff]; + *i = ((index < 6656) ? _tLvlTbl[index] : 0); + } else { + *i = 0; + } + + _phase += _phaseIncrement; + out += *o; +} + +void TownsPC98_FmSynthOperator::reset() { + keyOff(); + _timer = 0; + _keyScale2 = 0; + _currentLevel = 1023; + + frequency(0); + detune(0); + scaleRate(0); + multiple(0); + updatePhaseIncrement(); + attackRate(0); + decayRate(0); + releaseRate(0); + sustainRate(0); + feedbackLevel(0); + totalLevel(127); + ampModulation(false); +} + +bool TownsPC98_FmSynthOperator::scaleRate(uint8 value) { + value = 3 - value; + if (_keyScale1 != value) { + _keyScale1 = value; + return true; + } + + int k = _keyScale2; + int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0; + fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136; + fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0; + return false; +} + +class TownsPC98_FmSynthSquareSineSource { +public: + TownsPC98_FmSynthSquareSineSource(const uint32 timerbase, const uint32 rtt); + ~TownsPC98_FmSynthSquareSineSource(); + + void init(const int *rsTable, const int *rseTable); + void reset(); + void writeReg(uint8 address, uint8 value, bool force = false); + + void nextTick(int32 *buffer, uint32 bufferSize); + + void setVolumeIntern(int volA, int volB) { + _volumeA = volA; + _volumeB = volB; + } + void setVolumeChannelMasks(int channelMaskA, int channelMaskB) { + _volMaskA = channelMaskA; + _volMaskB = channelMaskB; + } + + uint8 chanEnable() const { + return _chanEnable; + } +private: + void updateRegs(); + + uint8 _updateRequestBuf[64]; + int _updateRequest; + int _rand; + + int8 _evpTimer; + uint32 _pReslt; + uint8 _attack; + + bool _evpUpdate, _cont; + + int _evpUpdateCnt; + uint8 _outN; + int _nTick; + + int32 *_tlTable; + int32 *_tleTable; + + const uint32 _tickLength; + uint32 _timer; + const uint32 _rtt; + + struct Channel { + int tick; + uint8 smp; + uint8 out; + + uint8 frqL; + uint8 frqH; + uint8 vol; + } _channels[3]; + + uint8 _noiseGenerator; + uint8 _chanEnable; + + uint8 **_reg; + + uint16 _volumeA; + uint16 _volumeB; + int _volMaskA; + int _volMaskB; + + bool _ready; +}; + +#ifndef DISABLE_PC98_RHYTHM_CHANNEL +class TownsPC98_FmSynthPercussionSource { +public: + TownsPC98_FmSynthPercussionSource(const uint32 timerbase, const uint32 rtt); + ~TownsPC98_FmSynthPercussionSource() { + delete[] _reg; + } + + void init(const uint8 *instrData = 0); + void reset(); + void writeReg(uint8 address, uint8 value); + + void nextTick(int32 *buffer, uint32 bufferSize); + + void setVolumeIntern(int volA, int volB) { + _volumeA = volA; + _volumeB = volB; + } + void setVolumeChannelMasks(int channelMaskA, int channelMaskB) { + _volMaskA = channelMaskA; + _volMaskB = channelMaskB; + } + +private: + struct RhtChannel { + const uint8 *data; + + const uint8 *start; + const uint8 *end; + const uint8 *pos; + uint32 size; + bool active; + uint8 level; + + int8 decState; + uint8 decStep; + + int16 samples[2]; + int out; + + uint8 startPosH; + uint8 startPosL; + uint8 endPosH; + uint8 endPosL; + }; + + void recalcOuput(RhtChannel *ins); + void advanceInput(RhtChannel *ins); + + RhtChannel _rhChan[6]; + + uint8 _totalLevel; + + const uint32 _tickLength; + uint32 _timer; + const uint32 _rtt; + + uint8 **_reg; + + uint16 _volumeA; + uint16 _volumeB; + int _volMaskA; + int _volMaskB; + + bool _ready; +}; +#endif // DISABLE_PC98_RHYTHM_CHANNEL + +TownsPC98_FmSynthSquareSineSource::TownsPC98_FmSynthSquareSineSource(const uint32 timerbase, const uint32 rtt) : _tlTable(0), + _rtt(rtt), _tleTable(0), _updateRequest(-1), _tickLength(timerbase * 27), _ready(0), _reg(0), _rand(1), _outN(1), + _nTick(0), _evpUpdateCnt(0), _evpTimer(0x1f), _pReslt(0x1f), _attack(0), _cont(false), _evpUpdate(true), + _timer(0), _noiseGenerator(0), _chanEnable(0), + _volMaskA(0), _volMaskB(0), _volumeA(Audio::Mixer::kMaxMixerVolume), _volumeB(Audio::Mixer::kMaxMixerVolume) { + + memset(_channels, 0, sizeof(_channels)); + memset(_updateRequestBuf, 0, sizeof(_updateRequestBuf)); + _reg = new uint8 *[11]; + + _reg[0] = &_channels[0].frqL; + _reg[1] = &_channels[0].frqH; + _reg[2] = &_channels[1].frqL; + _reg[3] = &_channels[1].frqH; + _reg[4] = &_channels[2].frqL; + _reg[5] = &_channels[2].frqH; + _reg[6] = &_noiseGenerator; + _reg[7] = &_chanEnable; + _reg[8] = &_channels[0].vol; + _reg[9] = &_channels[1].vol; + _reg[10] = &_channels[2].vol; + + reset(); +} + +TownsPC98_FmSynthSquareSineSource::~TownsPC98_FmSynthSquareSineSource() { + delete[] _tlTable; + delete[] _tleTable; + delete[] _reg; +} + +void TownsPC98_FmSynthSquareSineSource::init(const int *rsTable, const int *rseTable) { + if (_ready) { + reset(); + return; + } + + delete[] _tlTable; + delete[] _tleTable; + _tlTable = new int32[16]; + _tleTable = new int32[32]; + float a, b, d; + d = 801.0f; + + for (int i = 0; i < 16; i++) { + b = 1.0f / rsTable[i]; + a = 1.0f / d + b + 1.0f / 1000.0f; + float v = (b / a) * 32767.0f; + _tlTable[i] = (int32) v; + + b = 1.0f / rseTable[i]; + a = 1.0f / d + b + 1.0f / 1000.0f; + v = (b / a) * 32767.0f; + _tleTable[i] = (int32) v; + } + + for (int i = 16; i < 32; i++) { + b = 1.0f / rseTable[i]; + a = 1.0f / d + b + 1.0f / 1000.0f; + float v = (b / a) * 32767.0f; + _tleTable[i] = (int32) v; + } + + _ready = true; +} + +void TownsPC98_FmSynthSquareSineSource::reset() { + _rand = 1; + _outN = 1; + _updateRequest = -1; + _nTick = _evpUpdateCnt = 0; + _evpTimer = 0x1f; + _pReslt = 0x1f; + _attack = 0; + _cont = false; + _evpUpdate = true; + _timer = 0; + + for (int i = 0; i < 3; i++) { + _channels[i].tick = 0; + _channels[i].smp = _channels[i].out = 0; + } + + for (int i = 0; i < 14; i++) + writeReg(i, 0, true); + + writeReg(7, 0xbf, true); +} + +void TownsPC98_FmSynthSquareSineSource::writeReg(uint8 address, uint8 value, bool force) { + if (!_ready) + return; + + if (address > 10 || *_reg[address] == value) { + if ((address == 11 || address == 12 || address == 13) && value) + warning("TownsPC98_FmSynthSquareSineSource: unsupported reg address: %d", address); + return; + } + + if (!force) { + if (_updateRequest >= 63) { + warning("TownsPC98_FmSynthSquareSineSource: event buffer overflow"); + _updateRequest = -1; + } + _updateRequestBuf[++_updateRequest] = value; + _updateRequestBuf[++_updateRequest] = address; + return; + } + + *_reg[address] = value; +} + +void TownsPC98_FmSynthSquareSineSource::nextTick(int32 *buffer, uint32 bufferSize) { + if (!_ready) + return; + + for (uint32 i = 0; i < bufferSize; i++) { + _timer += _tickLength; + while (_timer > _rtt) { + _timer -= _rtt; + + if (++_nTick >= (_noiseGenerator & 0x1f)) { + if ((_rand + 1) & 2) + _outN ^= 1; + + _rand = (((_rand & 1) ^ ((_rand >> 3) & 1)) << 16) | (_rand >> 1); + _nTick = 0; + } + + for (int ii = 0; ii < 3; ii++) { + if (++_channels[ii].tick >= (((_channels[ii].frqH & 0x0f) << 8) | _channels[ii].frqL)) { + _channels[ii].tick = 0; + _channels[ii].smp ^= 1; + } + _channels[ii].out = (_channels[ii].smp | ((_chanEnable >> ii) & 1)) & (_outN | ((_chanEnable >> (ii + 3)) & 1)); + } + + if (_evpUpdate) { + if (++_evpUpdateCnt >= 0) { + _evpUpdateCnt = 0; + + if (--_evpTimer < 0) { + if (_cont) { + _evpTimer &= 0x1f; + } else { + _evpUpdate = false; + _evpTimer = 0; + } + } + } + } + _pReslt = _evpTimer ^ _attack; + updateRegs(); + } + + int32 finOut = 0; + for (int ii = 0; ii < 3; ii++) { + int32 finOutTemp = ((_channels[ii].vol >> 4) & 1) ? _tleTable[_channels[ii].out ? _pReslt : 0] : _tlTable[_channels[ii].out ? (_channels[ii].vol & 0x0f) : 0]; + + if ((1 << ii) & _volMaskA) + finOutTemp = (finOutTemp * _volumeA) / Audio::Mixer::kMaxMixerVolume; + + if ((1 << ii) & _volMaskB) + finOutTemp = (finOutTemp * _volumeB) / Audio::Mixer::kMaxMixerVolume; + + finOut += finOutTemp; + } + + finOut /= 3; + + buffer[i << 1] += finOut; + buffer[(i << 1) + 1] += finOut; + } +} + +void TownsPC98_FmSynthSquareSineSource::updateRegs() { + for (int i = 0; i < _updateRequest;) { + uint8 b = _updateRequestBuf[i++]; + uint8 a = _updateRequestBuf[i++]; + writeReg(a, b, true); + } + _updateRequest = -1; +} + +#ifndef DISABLE_PC98_RHYTHM_CHANNEL +TownsPC98_FmSynthPercussionSource::TownsPC98_FmSynthPercussionSource(const uint32 timerbase, const uint32 rtt) : + _rtt(rtt), _tickLength(timerbase * 2), _timer(0), _ready(false), _volMaskA(0), _volMaskB(0), _volumeA(Audio::Mixer::kMaxMixerVolume), _volumeB(Audio::Mixer::kMaxMixerVolume) { + + memset(_rhChan, 0, sizeof(RhtChannel) * 6); + _reg = new uint8 *[40]; + + _reg[0] = _reg[1] = _reg[2] = _reg[3] = _reg[4] = _reg[5] = _reg[6] = _reg[7] = _reg[8] = _reg[9] = _reg[10] = _reg[11] = _reg[12] = _reg[13] = _reg[14] = _reg[15] = 0; + _reg[16] = &_rhChan[0].startPosL; + _reg[17] = &_rhChan[1].startPosL; + _reg[18] = &_rhChan[2].startPosL; + _reg[19] = &_rhChan[3].startPosL; + _reg[20] = &_rhChan[4].startPosL; + _reg[21] = &_rhChan[5].startPosL; + _reg[22] = &_rhChan[0].startPosH; + _reg[23] = &_rhChan[1].startPosH; + _reg[24] = &_rhChan[2].startPosH; + _reg[25] = &_rhChan[3].startPosH; + _reg[26] = &_rhChan[4].startPosH; + _reg[27] = &_rhChan[5].startPosH; + _reg[28] = &_rhChan[0].endPosL; + _reg[29] = &_rhChan[1].endPosL; + _reg[30] = &_rhChan[2].endPosL; + _reg[31] = &_rhChan[3].endPosL; + _reg[32] = &_rhChan[4].endPosL; + _reg[33] = &_rhChan[5].endPosL; + _reg[34] = &_rhChan[0].endPosH; + _reg[35] = &_rhChan[1].endPosH; + _reg[36] = &_rhChan[2].endPosH; + _reg[37] = &_rhChan[3].endPosH; + _reg[38] = &_rhChan[4].endPosH; + _reg[39] = &_rhChan[5].endPosH; +} + +void TownsPC98_FmSynthPercussionSource::init(const uint8 *instrData) { + if (_ready) { + reset(); + return; + } + + const uint8 *start = instrData; + const uint8 *pos = start; + + if (instrData) { + for (int i = 0; i < 6; i++) { + _rhChan[i].data = start + READ_BE_UINT16(pos); + pos += 2; + _rhChan[i].size = READ_BE_UINT16(pos); + pos += 2; + } + reset(); + _ready = true; + } else { + memset(_rhChan, 0, sizeof(RhtChannel) * 6); + _ready = false; + } +} + +void TownsPC98_FmSynthPercussionSource::reset() { + _timer = 0; + _totalLevel = 63; + + for (int i = 0; i < 6; i++) { + RhtChannel *s = &_rhChan[i]; + s->pos = s->start = s->data; + s->end = s->data + s->size; + s->active = false; + s->level = 0; + s->out = 0; + s->decStep = 1; + s->decState = 0; + s->samples[0] = s->samples[1] = 0; + s->startPosH = s->startPosL = s->endPosH = s->endPosL = 0; + } +} + +void TownsPC98_FmSynthPercussionSource::writeReg(uint8 address, uint8 value) { + if (!_ready) + return; + + uint8 h = address >> 4; + uint8 l = address & 15; + + if (address > 15) + *_reg[address] = value; + + if (address == 0) { + if (value & 0x80) { + //key off + for (int i = 0; i < 6; i++) { + if ((value >> i) & 1) + _rhChan[i].active = false; + } + } else { + //key on + for (int i = 0; i < 6; i++) { + if ((value >> i) & 1) { + RhtChannel *s = &_rhChan[i]; + s->pos = s->start; + s->active = true; + s->out = 0; + s->samples[0] = s->samples[1] = 0; + s->decStep = 1; + s->decState = 0; + } + } + } + } else if (address == 1) { + // total level + _totalLevel = (value & 63) ^ 63; + for (int i = 0; i < 6; i++) + recalcOuput(&_rhChan[i]); + } else if (!h && l & 8) { + // instrument level + l &= 7; + _rhChan[l].level = (value & 0x1f) ^ 0x1f; + recalcOuput(&_rhChan[l]); + } else if (h & 3) { + l &= 7; + if (h == 1) { + // set start offset + _rhChan[l].start = _rhChan[l].data + ((_rhChan[l].startPosH << 8 | _rhChan[l].startPosL) << 8); + } else if (h == 2) { + // set end offset + _rhChan[l].end = _rhChan[l].data + ((_rhChan[l].endPosH << 8 | _rhChan[l].endPosL) << 8) + 255; + } + } +} + +void TownsPC98_FmSynthPercussionSource::nextTick(int32 *buffer, uint32 bufferSize) { + if (!_ready) + return; + + for (uint32 i = 0; i < bufferSize; i++) { + _timer += _tickLength; + while (_timer > _rtt) { + _timer -= _rtt; + + for (int ii = 0; ii < 6; ii++) { + RhtChannel *s = &_rhChan[ii]; + if (s->active) { + recalcOuput(s); + if (s->decStep) { + advanceInput(s); + if (s->pos == s->end) + s->active = false; + } + s->decStep ^= 1; + } + } + } + + int32 finOut = 0; + + for (int ii = 0; ii < 6; ii++) { + if (_rhChan[ii].active) + finOut += _rhChan[ii].out; + } + + finOut <<= 1; + + if (1 & _volMaskA) + finOut = (finOut * _volumeA) / Audio::Mixer::kMaxMixerVolume; + + if (1 & _volMaskB) + finOut = (finOut * _volumeB) / Audio::Mixer::kMaxMixerVolume; + + buffer[i << 1] += finOut; + buffer[(i << 1) + 1] += finOut; + } +} + +void TownsPC98_FmSynthPercussionSource::recalcOuput(RhtChannel *ins) { + uint32 s = _totalLevel + ins->level; + uint32 x = s > 62 ? 0 : (1 + (s >> 3)); + int32 y = s > 62 ? 0 : (15 - (s & 7)); + ins->out = ((ins->samples[ins->decStep] * y) >> x) & ~3; +} + +void TownsPC98_FmSynthPercussionSource::advanceInput(RhtChannel *ins) { + static const int8 adjustIndex[] = { -1, -1, -1, -1, 2, 5, 7, 9 }; + + static const int16 stepTable[] = { + 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, + 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, + 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552 + }; + + uint8 cur = (int8)*ins->pos++; + + for (int i = 0; i < 2; i++) { + int b = (2 * (cur & 7) + 1) * stepTable[ins->decState] / 8; + ins->samples[i] = CLIP(ins->samples[i ^ 1] + (cur & 8 ? b : -b), -2048, 2047); + ins->decState = CLIP(ins->decState + adjustIndex[cur & 7], 0, 48); + cur >>= 4; + } +} +#endif // DISABLE_PC98_RHYTHM_CHANNEL + +TownsPC98_FmSynth::TownsPC98_FmSynth(Audio::Mixer *mixer, EmuType type) : + _mixer(mixer), + _chanInternal(0), _ssg(0), +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + _prc(0), +#endif + _numChan(type == kType26 ? 3 : 6), _numSSG(type == kTypeTowns ? 0 : 3), + _hasPercussion(type == kType86 ? true : false), + _oprRates(0), _oprRateshift(0), _oprAttackDecay(0), _oprFrq(0), _oprSinTbl(0), _oprLevelOut(0), _oprDetune(0), + _rtt(type == kTypeTowns ? 0x514767 : 0x5B8D80), _baserate(55125.0f / (float)mixer->getOutputRate()), + _volMaskA(0), _volMaskB(0), _volumeA(255), _volumeB(255), + _regProtectionFlag(false), _ready(false) { + + memset(&_timers[0], 0, sizeof(ChipTimer)); + memset(&_timers[1], 0, sizeof(ChipTimer)); + + _timers[0].cb = _timers[1].cb = &TownsPC98_FmSynth::idleTimerCallback; + _timerbase = (uint32)(_baserate * 1000000.0f); +} + +TownsPC98_FmSynth::~TownsPC98_FmSynth() { + if (_ready) + deinit(); + + delete _ssg; +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + delete _prc; +#endif + delete[] _chanInternal; + + delete[] _oprRates; + delete[] _oprRateshift; + delete[] _oprFrq; + delete[] _oprAttackDecay; + delete[] _oprSinTbl; + delete[] _oprLevelOut; + delete[] _oprDetune; +} + +bool TownsPC98_FmSynth::init() { + if (_ready) { + reset(); + return true; + } + + generateTables(); + + _chanInternal = new ChanInternal[_numChan]; + for (int i = 0; i < _numChan; i++) { + memset(&_chanInternal[i], 0, sizeof(ChanInternal)); + for (int j = 0; j < 4; ++j) + _chanInternal[i].opr[j] = new TownsPC98_FmSynthOperator(_timerbase, _rtt, _oprRates, _oprRateshift, _oprAttackDecay, _oprFrq, _oprSinTbl, _oprLevelOut, _oprDetune); + } + + if (_numSSG) { + _ssg = new TownsPC98_FmSynthSquareSineSource(_timerbase, _rtt); + _ssg->init(&_ssgTables[0], &_ssgTables[16]); + } + +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + if (_hasPercussion) { + _prc = new TownsPC98_FmSynthPercussionSource(_timerbase, _rtt); + _prc->init(_percussionData); + } +#endif + + _timers[0].cb = &TownsPC98_FmSynth::timerCallbackA; + _timers[1].cb = &TownsPC98_FmSynth::timerCallbackB; + + _mixer->playStream(Audio::Mixer::kPlainSoundType, + &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + + _ready = true; + + return true; +} + +void TownsPC98_FmSynth::reset() { + Common::StackLock lock(_mutex); + for (int i = 0; i < _numChan; i++) { + for (int ii = 0; ii < 4; ii++) + _chanInternal[i].opr[ii]->reset(); + memset(_chanInternal[i].feedbuf, 0, 3); + _chanInternal[i].algorithm = 0; + _chanInternal[i].frqTemp = 0; + _chanInternal[i].enableLeft = _chanInternal[i].enableRight = true; + _chanInternal[i].updateEnvelopeParameters = false; + } + + writeReg(0, 0x27, 0x33); + + if (_ssg) + _ssg->reset(); + +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + if (_prc) + _prc->reset(); +#endif +} + +void TownsPC98_FmSynth::writeReg(uint8 part, uint8 regAddress, uint8 value) { + if (_regProtectionFlag || !_ready) + return; + + static const uint8 oprOrdr[] = { 0, 2, 1, 3 }; + + Common::StackLock lock(_mutex); + + uint8 h = regAddress & 0xf0; + uint8 l = (regAddress & 0x0f); + + ChanInternal *c = 0; + TownsPC98_FmSynthOperator **co = 0; + TownsPC98_FmSynthOperator *o = 0; + + if (regAddress > 0x2F) { + c = &_chanInternal[(l & 3) + 3 * part]; + co = c->opr; + o = c->opr[oprOrdr[(l - (l & 3)) >> 2]]; + } else if (regAddress == 0x28) { + c = &_chanInternal[(value & 3) + ((value & 4) ? 3 : 0)]; + co = c->opr; + } + + switch (h) { + case 0x00: + // ssg + if (_ssg) + _ssg->writeReg(l, value); + break; + case 0x10: +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + // pcm rhythm channel + if (_prc) + _prc->writeReg(l, value); +#endif + break; + case 0x20: + if (l == 8) { + // Key on/off + for (int i = 0; i < 4; i++) { + if ((value >> (4 + i)) & 1) + co[oprOrdr[i]]->keyOn(); + else + co[oprOrdr[i]]->keyOff(); + } + } else if (l == 4) { + // Timer A + _timers[0].value = (_timers[0].value & 3) | (value << 2); + } else if (l == 5) { + // Timer A + _timers[0].value = (_timers[0].value & 0x3fc) | (value & 3); + } else if (l == 6) { + // Timer B + _timers[1].value = value & 0xff; + } else if (l == 7) { + if (value & 1) { + float spc = (float)(0x400 - _timers[0].value) / _baserate; + if (spc < 1) { + warning("TownsPC98_FmSynth: Invalid Timer A setting: %d", _timers[0].value); + spc = 1; + } + + _timers[0].smpPerCb = (int32) spc; + _timers[0].smpPerCbRem = (uint32)((spc - (float)_timers[0].smpPerCb) * 1000000.0f); + _timers[0].smpTillCb = _timers[0].smpPerCb; + _timers[0].smpTillCbRem = _timers[0].smpPerCbRem; + _timers[0].enabled = true; + } else { + _timers[0].enabled = false; + } + + if (value & 2) { + float spc = (float)(0x100 - _timers[1].value) * 16.0f / _baserate; + if (spc < 1) { + warning("TownsPC98_FmSynth: Invalid Timer B setting: %d", _timers[1].value); + spc = 1; + } + + _timers[1].smpPerCb = (int32) spc; + _timers[1].smpPerCbRem = (uint32)((spc - (float)_timers[1].smpPerCb) * 1000000.0f); + _timers[1].smpTillCb = _timers[1].smpPerCb; + _timers[1].smpTillCbRem = _timers[1].smpPerCbRem; + _timers[1].enabled = true; + } else { + _timers[1].enabled = false; + } + + if (value & 0x10) { + _timers[0].smpTillCb = _timers[0].smpPerCb; + _timers[0].smpTillCbRem = _timers[0].smpTillCbRem; + } + + if (value & 0x20) { + _timers[1].smpTillCb = _timers[1].smpPerCb; + _timers[1].smpTillCbRem = _timers[1].smpTillCbRem; + } + } else if (l == 2) { + // LFO + if (value & 8) + warning("TownsPC98_FmSynth: TRYING TO USE LFO (NOT SUPPORTED)"); + } else if (l == 10 || l == 11) { + // DAC + if (l == 11 && (value & 0x80)) + warning("TownsPC98_FmSynth: TRYING TO USE DAC (NOT SUPPORTED)"); + } + break; + + case 0x30: + // detune, multiple + o->detune((value >> 4) & 7); + o->multiple(value & 0x0f); + c->updateEnvelopeParameters = true; + break; + + case 0x40: + // total level + o->totalLevel(value & 0x7f); + break; + + case 0x50: + // rate scaling, attack rate + o->attackRate(value & 0x1f); + if (o->scaleRate(value >> 6)) + c->updateEnvelopeParameters = true; + break; + + case 0x60: + // first decay rate, amplitude modulation + o->decayRate(value & 0x1f); + o->ampModulation(value & 0x80 ? true : false); + break; + + case 0x70: + // secondary decay rate + o->sustainRate(value & 0x1f); + break; + + case 0x80: + // secondary amplitude, release rate; + o->sustainLevel(value >> 4); + o->releaseRate(value & 0x0f); + break; + + case 0x90: + warning("TownsPC98_FmSynth: TRYING TO USE SSG ENVELOPE SHAPES (NOT SUPPORTED)"); + break; + + case 0xa0: + // frequency + l &= ~3; + if (l == 0) { + c->frqTemp = (c->frqTemp & 0xff00) | value; + c->updateEnvelopeParameters = true; + for (int i = 0; i < 4; i++) + co[i]->frequency(c->frqTemp); + } else if (l == 4) { + c->frqTemp = (c->frqTemp & 0xff) | (value << 8); + } else if (l == 8) { + // Ch 3/6 special mode frq + warning("TownsPC98_FmSynth: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)"); + } else if (l == 12) { + // Ch 3/6 special mode frq + warning("TownsPC98_FmSynth: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)"); + } + break; + + case 0xb0: + l &= ~3; + if (l == 0) { + // feedback, _algorithm + co[0]->feedbackLevel((value >> 3) & 7); + c->algorithm = value & 7; + } else if (l == 4) { + // stereo, LFO sensitivity + c->enableLeft = value & 0x80 ? true : false; + c->enableRight = value & 0x40 ? true : false; + c->ampModSensitivity((value & 0x30) >> 4); + c->frqModSensitivity(value & 3); + } + break; + + default: + warning("TownsPC98_FmSynth: UNKNOWN ADDRESS %d", regAddress); + } +} + +int TownsPC98_FmSynth::readBuffer(int16 *buffer, const int numSamples) { + Common::StackLock lock(_mutex); + + memset(buffer, 0, sizeof(int16) * numSamples); + int32 *tmp = new int32[numSamples]; + int32 *tmpStart = tmp; + memset(tmp, 0, sizeof(int32) * numSamples); + int32 samplesLeft = numSamples >> 1; + + while (_ready && samplesLeft) { + int32 render = samplesLeft; + + for (int i = 0; i < 2; i++) { + if (_timers[i].enabled && _timers[i].cb) { + if (!_timers[i].smpTillCb) { + (this->*_timers[i].cb)(); + _timers[i].smpTillCb = _timers[i].smpPerCb; + + _timers[i].smpTillCbRem += _timers[i].smpPerCbRem; + if (_timers[i].smpTillCbRem >= _timerbase) { + _timers[i].smpTillCb++; + _timers[i].smpTillCbRem -= _timerbase; + } + } + render = MIN(render, _timers[i].smpTillCb); + } + } + + samplesLeft -= render; + + for (int i = 0; i < 2; i++) { + if (_timers[i].enabled && _timers[i].cb) { + _timers[i].smpTillCb -= render; + } + } + + nextTick(tmp, render); + + if (_ssg) + _ssg->nextTick(tmp, render); +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + if (_prc) + _prc->nextTick(tmp, render); +#endif + + nextTickEx(tmp, render); + + for (int i = 0; i < render; ++i) { + int32 l = CLIP(tmp[i << 1], -32767, 32767); + buffer[i << 1] = (int16) l; + int32 r = CLIP(tmp[(i << 1) + 1], -32767, 32767); + buffer[(i << 1) + 1] = (int16) r; + } + + buffer += (render << 1); + tmp += (render << 1); + } + + delete[] tmpStart; + return numSamples; +} + +void TownsPC98_FmSynth::deinit() { + _ready = false; + _mixer->stopHandle(_soundHandle); + _timers[0].cb = _timers[1].cb = &TownsPC98_FmSynth::idleTimerCallback; +} + +uint8 TownsPC98_FmSynth::readSSGStatus() { + return _ssg->chanEnable(); +} + +void TownsPC98_FmSynth::setVolumeIntern(int volA, int volB) { + Common::StackLock lock(_mutex); + _volumeA = CLIP(volA, 0, Audio::Mixer::kMaxMixerVolume); + _volumeB = CLIP(volB, 0, Audio::Mixer::kMaxMixerVolume); + if (_ssg) + _ssg->setVolumeIntern(_volumeA, _volumeB); +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + if (_prc) + _prc->setVolumeIntern(_volumeA, _volumeB); +#endif +} + +void TownsPC98_FmSynth::setVolumeChannelMasks(int channelMaskA, int channelMaskB) { + Common::StackLock lock(_mutex); + _volMaskA = channelMaskA; + _volMaskB = channelMaskB; + if (_ssg) + _ssg->setVolumeChannelMasks(_volMaskA >> _numChan, _volMaskB >> _numChan); +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + if (_prc) + _prc->setVolumeChannelMasks(_volMaskA >> (_numChan + _numSSG), _volMaskB >> (_numChan + _numSSG)); +#endif +} + +void TownsPC98_FmSynth::generateTables() { + delete[] _oprRates; + _oprRates = new uint8[128]; + + WRITE_BE_UINT32(_oprRates + 32, _numChan == 6 ? 0x90900000 : 0x00081018); + WRITE_BE_UINT32(_oprRates + 36, _numChan == 6 ? 0x00001010 : 0x00081018); + memset(_oprRates, 0x90, 32); + memset(_oprRates + 96, 0x80, 32); + uint8 *dst = (uint8 *)_oprRates + 40; + for (int i = 0; i < 40; i += 4) + WRITE_BE_UINT32(dst + i, 0x00081018); + for (int i = 0; i < 48; i += 4) + WRITE_BE_UINT32(dst + i, 0x00081018); + dst += 40; + for (uint8 i = 0; i < 16; i ++) { + uint8 v = (i < 12) ? i : 12; + *dst++ = ((4 + v) << 3); + } + + delete[] _oprRateshift; + _oprRateshift = new uint8[128]; + memset(_oprRateshift, 0, 128); + dst = (uint8 *)_oprRateshift + 32; + for (int i = 11; i; i--) { + memset(dst, i, 4); + dst += 4; + } + + delete[] _oprFrq; + _oprFrq = new uint32[0x1000]; + for (uint32 i = 0; i < 0x1000; i++) + _oprFrq[i] = (uint32)(_baserate * (float)(i << 11)); + + delete[] _oprAttackDecay; + _oprAttackDecay = new uint8[152]; + memset(_oprAttackDecay, 0, 152); + for (int i = 0; i < 36; i++) + WRITE_BE_UINT32(_oprAttackDecay + (i << 2), _adtStat[i]); + + delete[] _oprSinTbl; + _oprSinTbl = new uint32[1024]; + for (int i = 0; i < 1024; i++) { + double val = sin((double)(((i << 1) + 1) * PI / 1024.0)); + double d_dcb = log(1.0 / (double)ABS(val)) / log(2.0) * 256.0; + int32 i_dcb = (int32)(2.0 * d_dcb); + i_dcb = (i_dcb & 1) ? (i_dcb >> 1) + 1 : (i_dcb >> 1); + _oprSinTbl[i] = (i_dcb << 1) + (val >= 0.0 ? 0 : 1); + } + + delete[] _oprLevelOut; + _oprLevelOut = new int32[0x1a00]; + for (int i = 0; i < 256; i++) { + double val = floor(65536.0 / pow(2.0, 0.00390625 * (double)(1 + i))); + int32 val_int = ((int32) val) >> 4; + _oprLevelOut[i << 1] = (val_int & 1) ? ((val_int >> 1) + 1) << 2 : (val_int >> 1) << 2; + _oprLevelOut[(i << 1) + 1] = -_oprLevelOut[i << 1]; + for (int ii = 1; ii < 13; ++ii) { + _oprLevelOut[(i << 1) + (ii << 9)] = _oprLevelOut[i << 1] >> ii; + _oprLevelOut[(i << 1) + (ii << 9) + 1] = -_oprLevelOut[(i << 1) + (ii << 9)]; + } + } + + uint8 *dtt = new uint8[128]; + memset(dtt, 0, 36); + memset(dtt + 36, 1, 8); + memcpy(dtt + 44, _detSrc, 84); + + delete[] _oprDetune; + _oprDetune = new int32[256]; + for (int i = 0; i < 128; i++) { + _oprDetune[i] = (int32)((float)dtt[i] * _baserate * 64.0); + _oprDetune[i + 128] = -_oprDetune[i]; + } + + delete[] dtt; +} + +void TownsPC98_FmSynth::nextTick(int32 *buffer, uint32 bufferSize) { + if (!_ready) + return; + + for (int i = 0; i < _numChan; i++) { + TownsPC98_FmSynthOperator **o = _chanInternal[i].opr; + + if (_chanInternal[i].updateEnvelopeParameters) { + _chanInternal[i].updateEnvelopeParameters = false; + for (int ii = 0; ii < 4 ; ii++) + o[ii]->updatePhaseIncrement(); + } + + for (uint32 ii = 0; ii < bufferSize ; ii++) { + int32 phbuf1, phbuf2, output; + phbuf1 = phbuf2 = output = 0; + + int32 *leftSample = &buffer[ii * 2]; + int32 *rightSample = &buffer[ii * 2 + 1]; + int32 *del = &_chanInternal[i].feedbuf[2]; + int32 *feed = _chanInternal[i].feedbuf; + + switch (_chanInternal[i].algorithm) { + case 0: + o[0]->generateOutput(0, feed, phbuf1); + o[2]->generateOutput(*del, 0, phbuf2); + *del = 0; + o[1]->generateOutput(phbuf1, 0, *del); + o[3]->generateOutput(phbuf2, 0, output); + break; + case 1: + o[0]->generateOutput(0, feed, phbuf1); + o[2]->generateOutput(*del, 0, phbuf2); + o[1]->generateOutput(0, 0, phbuf1); + o[3]->generateOutput(phbuf2, 0, output); + *del = phbuf1; + break; + case 2: + o[0]->generateOutput(0, feed, phbuf2); + o[2]->generateOutput(*del, 0, phbuf2); + o[1]->generateOutput(0, 0, phbuf1); + o[3]->generateOutput(phbuf2, 0, output); + *del = phbuf1; + break; + case 3: + o[0]->generateOutput(0, feed, phbuf2); + o[2]->generateOutput(0, 0, *del); + o[1]->generateOutput(phbuf2, 0, phbuf1); + o[3]->generateOutput(*del, 0, output); + *del = phbuf1; + break; + case 4: + o[0]->generateOutput(0, feed, phbuf1); + o[2]->generateOutput(0, 0, phbuf2); + o[1]->generateOutput(phbuf1, 0, output); + o[3]->generateOutput(phbuf2, 0, output); + *del = 0; + break; + case 5: + o[0]->generateOutput(0, feed, phbuf1); + o[2]->generateOutput(*del, 0, output); + o[1]->generateOutput(phbuf1, 0, output); + o[3]->generateOutput(phbuf1, 0, output); + *del = phbuf1; + break; + case 6: + o[0]->generateOutput(0, feed, phbuf1); + o[2]->generateOutput(0, 0, output); + o[1]->generateOutput(phbuf1, 0, output); + o[3]->generateOutput(0, 0, output); + *del = 0; + break; + case 7: + o[0]->generateOutput(0, feed, output); + o[2]->generateOutput(0, 0, output); + o[1]->generateOutput(0, 0, output); + o[3]->generateOutput(0, 0, output); + *del = 0; + break; + }; + + int32 finOut = (output << 2) / ((_numChan + _numSSG - 3) / 3); + + if ((1 << i) & _volMaskA) + finOut = (finOut * _volumeA) / Audio::Mixer::kMaxMixerVolume; + + if ((1 << i) & _volMaskB) + finOut = (finOut * _volumeB) / Audio::Mixer::kMaxMixerVolume; + + if (_chanInternal[i].enableLeft) + *leftSample += finOut; + + if (_chanInternal[i].enableRight) + *rightSample += finOut; + } + } +} + +const uint32 TownsPC98_FmSynth::_adtStat[] = { + 0x00010001, 0x00010001, 0x00010001, 0x01010001, + 0x00010101, 0x00010101, 0x00010101, 0x01010101, + 0x01010101, 0x01010101, 0x01010102, 0x01010102, + 0x01020102, 0x01020102, 0x01020202, 0x01020202, + 0x02020202, 0x02020202, 0x02020204, 0x02020204, + 0x02040204, 0x02040204, 0x02040404, 0x02040404, + 0x04040404, 0x04040404, 0x04040408, 0x04040408, + 0x04080408, 0x04080408, 0x04080808, 0x04080808, + 0x08080808, 0x08080808, 0x10101010, 0x10101010 +}; + +const uint8 TownsPC98_FmSynth::_detSrc[] = { + 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, + 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, + 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, + 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x10, 0x10, 0x10, 0x10, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, + 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x13, 0x14, + 0x16, 0x16, 0x16, 0x16 +}; + +const int TownsPC98_FmSynth::_ssgTables[] = { + 0x01202A, 0x0092D2, 0x006B42, 0x0053CB, 0x003DF8, 0x003053, 0x0022DA, 0x001A8C, + 0x00129B, 0x000DC1, 0x000963, 0x0006C9, 0x000463, 0x0002FA, 0x0001B6, 0x0000FB, + 0x0193B6, 0x01202A, 0x00CDB1, 0x0092D2, 0x007D7D, 0x006B42, 0x005ECD, 0x0053CB, + 0x00480F, 0x003DF8, 0x0036B9, 0x003053, 0x00290A, 0x0022DA, 0x001E6B, 0x001A8C, + 0x001639, 0x00129B, 0x000FFF, 0x000DC1, 0x000B5D, 0x000963, 0x0007FB, 0x0006C9, + 0x000575, 0x000463, 0x00039D, 0x0002FA, 0x000242, 0x0001B6, 0x00014C, 0x0000FB +}; + +#ifndef DISABLE_PC98_RHYTHM_CHANNEL +const uint8 TownsPC98_FmSynth::_percussionData[] = { + 0, 24, 1, 192, 1, 216, 2, 128, 4, 88, 23, 64, 27, 152, 1, 128, 29, 24, 2, 128, 31, 152, 0, 128, 136, 128, 128, 128, 0, 136, 97, 103, 153, 139, 34, 163, 72, 195, 27, 69, 1, 154, 137, 35, 8, 51, 169, 122, 164, 75, 133, 203, 81, 146, 168, 121, 185, 68, 202, 8, 33, 237, 49, 177, 12, 133, 140, 17, 160, 42, 161, 10, 0, 137, 176, 57, + 233, 41, 160, 136, 235, 65, 177, 137, 128, 26, 164, 28, 3, 157, 51, 137, 1, 152, 113, 161, 40, 146, 115, 192, 56, 5, 169, 66, 161, 56, 1, 50, 145, 59, 39, 168, 97, 1, 160, 57, 7, 153, 50, 153, 32, 2, 25, 129, 32, 20, 186, 66, 129, 24, 153, 164, 142, 130, 169, 153, 26, 242, 138, 217, 9, 128, 204, 58, 209, 172, 40, 176, 141, + 128, 155, 144, 203, 139, 0, 235, 9, 177, 172, 0, 185, 168, 138, 25, 240, 59, 211, 139, 19, 176, 90, 160, 17, 26, 132, 41, 1, 5, 25, 3, 50, 144, 115, 147, 42, 39, 152, 41, 3, 56, 193, 105, 130, 155, 66, 200, 26, 19, 218, 154, 49, 201, 171, 138, 176, 251, 139, 185, 172, 136, 189, 139, 145, 207, 41, 160, 171, 152, 186, 139, + 186, 141, 128, 218, 171, 51, 217, 170, 56, 163, 12, 4, 155, 81, 147, 42, 37, 152, 32, 54, 136, 49, 50, 48, 37, 32, 69, 0, 17, 50, 50, 83, 2, 16, 68, 20, 8, 66, 4, 154, 84, 145, 24, 33, 24, 32, 17, 18, 145, 32, 22, 168, 49, 163, 1, 33, 50, 184, 115, 129, 25, 66, 1, 24, 67, 2, 80, 35, 40, 53, 2, 65, 51, 19, 67, 37, 0, 52, 35, 49, 37, + 34, 49, 37, 17, 52, 17, 35, 35, 35, 34, 32, 49, 33, 152, 34, 145, 24, 24, 128, 138, 128, 184, 9, 177, 171, 168, 185, 155, 152, 172, 155, 186, 172, 185, 172, 155, 186, 173, 153, 202, 187, 185, 202, 170, 171, 202, 186, 169, 170, 170, 171, 139, 154, 171, 153, 154, 169, 10, 168, 154, 128, 168, 154, 0, 153, 152, 136, 137, + 128, 153, 0, 152, 8, 128, 137, 0, 136, 136, 8, 9, 8, 9, 8, 24, 153, 128, 136, 153, 144, 0, 161, 138, 1, 169, 136, 128, 160, 168, 152, 153, 138, 137, 154, 153, 153, 154, 153, 170, 168, 170, 185, 168, 169, 154, 169, 171, 153, 169, 170, 153, 152, 154, 153, 137, 169, 137, 136, 144, 152, 144, 128, 128, 144, 129, 129, 0, 33, + 0, 17, 17, 17, 33, 33, 18, 18, 34, 34, 34, 34, 34, 34, 35, 19, 35, 19, 35, 35, 18, 19, 18, 35, 18, 33, 0, 8, 8, 8, 8, 8, 8, 8, 160, 205, 65, 176, 171, 203, 16, 240, 95, 242, 120, 145, 156, 66, 177, 26, 19, 153, 9, 35, 35, 239, 56, 132, 138, 154, 50, 145, 203, 25, 32, 20, 237, 24, 130, 138, 160, 27, 39, 173, 50, 203, 64, 145, 139, + 18, 168, 48, 146, 171, 65, 18, 176, 12, 52, 128, 25, 5, 57, 240, 104, 161, 25, 129, 18, 188, 114, 160, 26, 36, 200, 154, 18, 1, 128, 186, 73, 162, 173, 32, 184, 25, 144, 137, 234, 8, 154, 32, 160, 158, 18, 187, 81, 2, 235, 41, 36, 144, 154, 17, 67, 128, 33, 160, 114, 146, 26, 37, 33, 232, 41, 130, 41, 178, 29, 50, 251, 24, + 1, 153, 138, 160, 76, 179, 155, 11, 0, 38, 252, 41, 146, 41, 178, 27, 193, 43, 39, 170, 136, 17, 129, 8, 49, 233, 48, 129, 11, 6, 26, 130, 136, 128, 64, 1, 248, 105, 145, 9, 16, 144, 140, 5, 25, 168, 16, 186, 48, 5, 171, 217, 57, 134, 171, 8, 34, 188, 20, 203, 41, 6, 155, 161, 89, 164, 140, 2, 136, 51, 202, 41, 131, 56, 144, + 8, 97, 144, 146, 13, 69, 200, 42, 130, 25, 152, 57, 6, 220, 88, 177, 26, 148, 9, 168, 8, 67, 192, 156, 65, 145, 137, 10, 4, 154, 18, 157, 67, 160, 154, 1, 50, 188, 82, 170, 82, 185, 49, 220, 97, 144, 10, 8, 16, 145, 9, 136, 18, 202, 51, 184, 141, 114, 179, 139, 24, 19, 8, 250, 121, 160, 40, 160, 10, 18, 152, 168, 42, 35, 216, + 187, 120, 145, 18, 156, 203, 84, 144, 9, 144, 26, 66, 161, 13, 1, 128, 17, 154, 18, 142, 6, 154, 65, 192, 29, 35, 186, 64, 192, 24, 9, 146, 56, 185, 16, 248, 121, 176, 40, 129, 136, 171, 96, 147, 140, 50, 203, 64, 144, 41, 128, 161, 187, 71, 200, 24, 129, 24, 217, 56, 20, 220, 24, 4, 169, 9, 1, 33, 201, 26, 134, 141, 51, 201, + 25, 16, 33, 235, 32, 144, 33, 153, 169, 99, 160, 11, 3, 136, 58, 210, 33, 203, 48, 163, 17, 219, 128, 140, 38, 8, 184, 141, 50, 131, 159, 33, 128, 153, 25, 18, 153, 88, 242, 43, 3, 9, 136, 157, 53, 202, 40, 145, 25, 2, 204, 105, 146, 156, 66, 152, 8, 153, 33, 128, 129, 136, 153, 50, 186, 55, 188, 51, 249, 64, 178, 27, 128, + 48, 177, 156, 18, 35, 175, 51, 189, 32, 51, 234, 155, 69, 184, 26, 2, 152, 9, 17, 136, 144, 137, 50, 235, 115, 216, 24, 2, 170, 67, 187, 49, 129, 155, 4, 27, 129, 56, 232, 43, 39, 203, 40, 3, 154, 169, 66, 184, 114, 224, 25, 2, 9, 128, 11, 35, 155, 18, 11, 202, 84, 169, 26, 5, 154, 8, 160, 98, 185, 17, 187, 50, 23, 188, 33, + 1, 139, 4, 154, 90, 147, 12, 3, 43, 2, 170, 171, 103, 193, 28, 132, 137, 8, 129, 24, 170, 50, 201, 42, 35, 202, 169, 52, 201, 33, 218, 40, 39, 203, 0, 40, 147, 29, 163, 139, 83, 185, 1, 4, 159, 34, 160, 12, 21, 155, 40, 129, 137, 58, 151, 13, 2, 136, 144, 16, 153, 40, 17, 131, 207, 51, 144, 140, 4, 154, 17, 146, 170, 73, 163, + 44, 164, 12, 152, 37, 203, 17, 128, 144, 139, 23, 154, 128, 138, 38, 216, 41, 1, 0, 233, 73, 131, 171, 49, 136, 9, 164, 46, 3, 171, 32, 0, 145, 157, 38, 187, 64, 176, 58, 134, 155, 18, 136, 217, 64, 1, 200, 140, 38, 153, 170, 66, 161, 8, 169, 65, 185, 98, 200, 41, 3, 155, 144, 58, 23, 187, 1, 145, 40, 147, 189, 32, 68, 249, + 1, 112, 255, 199, 195, 19, 108, 76, 187, 247, 247, 183, 40, 168, 212, 245, 199, 227, 68, 45, 59, 10, 145, 177, 198, 24, 130, 76, 26, 193, 180, 129, 0, 162, 42, 160, 199, 162, 0, 16, 152, 137, 132, 168, 195, 130, 162, 181, 227, 163, 161, 179, 211, 180, 179, 164, 128, 162, 161, 194, 164, 179, 40, 153, 195, 213, 146, 178, + 147, 176, 50, 186, 161, 196, 151, 58, 16, 28, 162, 160, 131, 122, 155, 33, 241, 146, 128, 40, 26, 128, 154, 36, 170, 89, 59, 9, 24, 144, 77, 161, 8, 177, 112, 139, 33, 232, 148, 24, 41, 61, 9, 26, 162, 32, 30, 58, 153, 32, 59, 73, 59, 11, 79, 137, 57, 9, 49, 30, 24, 153, 131, 25, 106, 61, 153, 73, 28, 56, 27, 41, 137, 148, + 76, 43, 74, 58, 13, 161, 3, 171, 149, 32, 77, 10, 74, 42, 168, 16, 0, 123, 138, 129, 162, 178, 225, 50, 140, 161, 0, 147, 10, 129, 41, 244, 210, 165, 1, 152, 24, 162, 184, 166, 32, 144, 59, 216, 132, 177, 8, 145, 67, 143, 146, 160, 183, 162, 130, 24, 192, 32, 225, 146, 144, 33, 44, 73, 30, 129, 137, 32, 76, 152, 25, 161, + 2, 154, 32, 177, 132, 232, 2, 136, 210, 128, 149, 177, 32, 58, 27, 168, 225, 133, 8, 44, 107, 136, 25, 136, 17, 26, 58, 46, 16, 11, 145, 17, 144, 79, 136, 144, 136, 145, 152, 33, 31, 162, 130, 200, 82, 153, 74, 137, 147, 26, 0, 13, 133, 170, 149, 16, 192, 0, 178, 0, 128, 152, 182, 150, 9, 16, 9, 137, 33, 59, 63, 10, 152, 32, + 179, 192, 5, 154, 228, 182, 145, 130, 144, 42, 128, 242, 2, 136, 41, 168, 17, 76, 57, 31, 129, 136, 17, 47, 8, 41, 138, 32, 138, 123, 59, 58, 10, 136, 161, 4, 46, 25, 145, 136, 129, 25, 56, 28, 91, 41, 154, 108, 9, 16, 44, 24, 137, 48, 15, 0, 194, 162, 41, 194, 56, 241, 163, 146, 0, 139, 7, 186, 150, 129, 152, 1, 208, 33, 176, + 136, 164, 163, 185, 7, 138, 130, 242, 162, 163, 177, 88, 136, 184, 166, 146, 0, 25, 25, 177, 199, 146, 16, 136, 9, 145, 178, 178, 0, 147, 138, 229, 18, 152, 25, 144, 163, 246, 162, 129, 129, 184, 5, 152, 178, 145, 148, 136, 146, 95, 152, 128, 144, 33, 170, 81, 11, 40, 202, 131, 0, 243, 24, 1, 11, 148, 42, 24, 163, 140, + 120, 9, 76, 58, 153, 145, 56, 30, 72, 46, 42, 9, 8, 57, 91, 76, 59, 26, 160, 129, 41, 76, 10, 57, 192, 163, 129, 16, 225, 2, 27, 40, 200, 48, 91, 226, 40, 145, 43, 177, 177, 182, 196, 145, 33, 184, 165, 17, 192, 163, 194, 129, 211, 128, 162, 197, 129, 0, 136, 211, 146, 8, 162, 144, 0, 167, 160, 1, 176, 150, 137, 1, 24, 243, + 0, 129, 145, 25, 123, 169, 130, 168, 132, 41, 63, 42, 136, 137, 120, 26, 136, 8, 24, 89, 29, 58, 177, 193, 147, 1, 26, 162, 176, 167, 180, 8, 49, 28, 29, 178, 162, 88, 43, 42, 57, 43, 61, 8, 29, 129, 128, 128, 123, 137, 24, 243, 16, 136, 16, 46, 0, 169, 149, 128, 1, 60, 153, 72, 154, 90, 25, 25, 25, 8, 91, 73, 12, 16, 137, 144, + 72, 11, 8, 167, 128, 129, 9, 138, 166, 193, 147, 162, 123, 137, 145, 1, 162, 26, 1, 219, 147, 129, 210, 147, 243, 1, 243, 16, 144, 145, 160, 131, 200, 4, 59, 75, 57, 218, 2, 178, 77, 24, 60, 11, 147, 10, 50, 141, 64, 27, 185, 122, 161, 41, 128, 90, 136, 24, 46, 16, 139, 16, 24, 28, 124, 9, 41, 8, 26, 121, 10, 42, 40, 139, 129, + 0, 201, 135, 137, 56, 176, 176, 35, 215, 145, 1, 26, 145, 144, 160, 135, 138, 1, 177, 146, 146, 161, 65, 242, 136, 164, 177, 1, 1, 186, 151, 208, 148, 129, 10, 32, 241, 145, 163, 178, 17, 168, 136, 151, 168, 2, 148, 185, 133, 176, 130, 129, 154, 163, 215, 0, 146, 136, 40, 211, 161, 131, 171, 81, 144, 170, 21, 184, 56, + 195, 168, 133, 177, 91, 16, 187, 5, 145, 153, 66, 172, 18, 177, 42, 120, 138, 27, 134, 26, 106, 42, 138, 146, 184, 66, 75, 46, 41, 168, 0, 145, 57, 91, 75, 27, 24, 27, 48, 169, 40, 122, 9, 109, 10, 8, 177, 146, 16, 74, 30, 129, 160, 162, 146, 41, 124, 138, 24, 145, 152, 3, 1, 14, 3, 139, 1, 192, 161, 151, 177, 122, 8, 10, 0, + 176, 130, 129, 27, 88, 225, 0, 2, 154, 129, 129, 193, 49, 203, 81, 153, 226, 33, 0, 30, 0, 176, 179, 18, 9, 96, 156, 162, 148, 160, 129, 2, 29, 195, 128, 0, 56, 156, 20, 232, 129, 128, 32, 10, 144, 74, 183, 9, 145, 162, 1, 162, 138, 23, 171, 1, 164, 224, 34, 43, 43, 177, 200, 135, 161, 91, 57, 154, 177, 148, 145, 146, 58, + 108, 136, 170, 35, 208, 177, 34, 128, 44, 129, 155, 151, 243, 16, 1, 154, 72, 193, 144, 18, 11, 122, 160, 153, 5, 192, 24, 130, 184, 132, 226, 0, 128, 153, 131, 181, 136, 65, 154, 128, 17, 170, 39, 28, 59, 144, 168, 80, 25, 47, 24, 26, 144, 32, 47, 41, 153, 161, 148, 8, 92, 9, 9, 129, 144, 33, 26, 47, 24, 137, 108, 25, 10, + 17, 10, 73, 75, 47, 24, 184, 48, 8, 45, 57, 138, 136, 150, 10, 48, 139, 136, 35, 203, 121, 8, 27, 179, 161, 106, 0, 29, 16, 176, 179, 3, 185, 19, 227, 41, 145, 168, 61, 197, 177, 20, 10, 57, 42, 250, 147, 196, 16, 41, 138, 24, 195, 208, 135, 137, 0, 145, 160, 2, 210, 146, 195, 177, 132, 136, 153, 167, 210, 146, 162, 40, 8, + 138, 148, 227, 145, 17, 137, 40, 169, 179, 130, 242, 2, 196, 9, 146, 145, 169, 167, 146, 130, 137, 136, 51, 220, 17, 163, 28, 74, 10, 76, 40, 140, 5, 137, 43, 18, 12, 107, 137, 40, 8, 201, 50, 0, 143, 3, 138, 161, 134, 138, 104, 169, 16, 162, 160, 121, 25, 28, 129, 152, 32, 56, 14, 16, 184, 146, 3, 46, 25, 176, 129, 179, + 193, 17, 130, 202, 135, 8, 57, 25, 154, 148, 184, 120, 9, 153, 211, 165, 24, 128, 26, 17, 242, 161, 18, 185, 81, 42, 11, 17, 12, 25, 181, 137, 66, 42, 47, 41, 184, 166, 129, 24, 91, 27, 136, 196, 0, 0, 74, 28, 178, 161, 149, 160, 32, 8, 225, 32, 128, 59, 8, 169, 50, 139, 47, 72, 186, 16, 132, 9, 122, 9, 160, 146, 144, 89, 153, + 10, 149, 178, 0, 121, 11, 146, 152, 162, 48, 13, 123, 177, 24, 0, 106, 27, 9, 144, 132, 12, 17, 0, 168, 0, 181, 56, 169, 129, 242, 195, 129, 17, 154, 64, 161, 244, 16, 137, 24, 144, 144, 164, 129, 75, 42, 176, 149, 9, 179, 148, 203, 4, 166, 136, 163, 128, 227, 163, 8, 57, 11, 30, 165, 0, 74, 59, 62, 9, 208, 131, 144, 40, 76, + 26, 27, 196, 129, 1, 25, 43, 49, 174, 67, 153, 136, 106, 152, 41, 25, 28, 2, 43, 44, 104, 45, 59, 8, 43, 128, 144, 120, 25, 12, 17, 152, 9, 130, 155, 151, 145, 74, 40, 13, 48, 192, 58, 90, 43, 43, 177, 146, 49, 31, 75, 24, 217, 131, 0, 76, 26, 152, 149, 161, 24, 74, 154, 193, 166, 145, 32, 27, 161, 164, 176, 135, 152, 24, 193, + 162, 146, 164, 58, 227, 193, 148, 161, 128, 18, 234, 130, 180, 145, 2, 200, 1, 163, 186, 98, 184, 129, 149, 153, 49, 42, 186, 151, 242, 129, 1, 43, 8, 177, 212, 165, 8, 40, 137, 24, 8, 144, 90, 9, 25, 48, 44, 46, 24, 138, 40, 144, 108, 58, 27, 128, 181, 128, 80, 29, 42, 152, 162, 130, 25, 106, 136, 11, 148, 8, 144, 128, 136, + 112, 139, 80, 153, 24, 136, 129, 46, 0, 60, 129, 208, 1, 3, 13, 57, 168, 144, 1, 242, 17, 9, 26, 2, 185, 27, 55, 140, 73, 137, 179, 16, 192, 3, 145, 143, 33, 9, 171, 135, 160, 17, 137, 10, 151, 168, 3, 178, 44, 17, 208, 144, 167, 0, 40, 155, 16, 167, 152, 18, 144, 26, 160, 199, 1, 136, 91, 136, 160, 178, 150, 161, 1, 10, 181, + 145, 161, 1, 145, 161, 198, 2, 9, 90, 137, 177, 160, 150, 40, 29, 129, 144, 145, 162, 57, 77, 169, 16, 148, 42, 42, 40, 141, 34, 170, 121, 154, 210, 131, 162, 107, 8, 9, 160, 195, 40, 73, 139, 18, 224, 162, 34, 139, 0, 244, 178, 163, 24, 26, 146, 194, 166, 49, 29, 42, 137, 130, 192, 16, 93, 128, 154, 19, 59, 11, 122, 11, + 146, 177, 120, 42, 26, 43, 164, 152, 17, 60, 63, 137, 128, 48, 10, 58, 92, 9, 59, 91, 75, 139, 32, 25, 25, 61, 74, 28, 177, 40, 130, 74, 29, 73, 168, 130, 128, 48, 14, 8, 77, 9, 25, 26, 179, 211, 32, 78, 26, 41, 152, 161, 180, 89, 59, 9, 153, 166, 160, 3, 26, 57, 106, 154, 88, 184, 40, 1, 27, 58, 73, 143, 131, 169, 3, 161, 184, + 122, 152, 16, 181, 145, 129, 17, 15, 129, 193, 147, 145, 192, 33, 193, 162, 183, 163, 136, 178, 129, 178, 197, 2, 41, 216, 131, 168, 163, 181, 226, 163, 178, 1, 33, 187, 166, 212, 129, 1, 27, 24, 162, 184, 151, 8, 16, 160, 144, 181, 210, 72, 168, 128, 32, 42, 25, 40, 142, 5, 185, 88, 58, 11, 58, 177, 32, 129, 63, 42, 136, + 186, 53, 29, 75, 58, 144, 144, 129, 77, 128, 11, 144, 133, 29, 40, 152, 24, 161, 129, 80, 155, 60, 3, 12, 89, 8, 60, 152, 152, 49, 136, 47, 57, 224, 129, 16, 41, 90, 139, 162, 147, 170, 51, 169, 27, 17, 95, 26, 26, 160, 5, 139, 48, 76, 10, 228, 146, 1, 136, 44, 161, 147, 209, 130, 137, 73, 224, 1, 162, 195, 32, 210, 177, 180, + 179, 148, 145, 154, 132, 242, 146, 1, 152, 32, 192, 1, 144, 155, 7, 177, 168, 5, 138, 178, 148, 152, 150, 136, 89, 152, 9, 41, 196, 145, 40, 28, 16, 8, 10, 178, 167, 24, 1, 44, 123, 137, 136, 145, 194, 48, 27, 74, 26, 192, 179, 135, 136, 88, 27, 10, 177, 163, 164, 128, 73, 24, 31, 8, 0, 192, 149, 144, 129, 9, 106, 41, 200, + 161, 151, 41, 138, 0, 24, 226, 162, 49, 42, 11, 90, 136, 136, 152, 17, 145, 10, 63, 40, 11, 56, 245, 162, 16, 26, 73, 11, 144, 135, 137, 58, 106, 10, 25, 8, 57, 137, 28, 33, 129, 156, 113, 10, 10, 161, 18, 8, 153, 77, 3, 217, 0, 1, 242, 128, 193, 18, 128, 75, 60, 178, 154, 37, 45, 58, 29, 144, 1, 184, 66, 41, 29, 8, 145, 10, + 194, 33, 148, 170, 107, 89, 139, 128, 163, 178, 16, 63, 59, 176, 144, 151, 129, 42, 74, 10, 129, 192, 2, 128, 154, 97, 192, 0, 177, 128, 178, 183, 16, 16, 155, 149, 145, 184, 84, 138, 8, 192, 161, 20, 225, 0, 130, 138, 165, 0, 28, 148, 153, 18, 209, 128, 88, 153, 89, 152, 9, 17, 9, 29, 130, 43, 122, 153, 24, 32, 202, 49, + 24, 43, 106, 154, 130, 193, 27, 51, 29, 28, 133, 138, 65, 11, 123, 25, 10, 40, 152, 44, 130, 26, 43, 148, 45, 73, 140, 33, 8, 153, 88, 128, 61, 144, 42, 59, 225, 128, 18, 155, 50, 75, 186, 20, 202, 120, 144, 42, 92, 176, 162, 165, 25, 2, 169, 152, 135, 185, 19, 152, 8, 146, 160, 123, 195, 137, 132, 209, 0, 16, 11, 2, 242, + 146, 164, 152, 73, 193, 136, 130, 178, 1, 136, 169, 23, 169, 128, 164, 242, 129, 178, 129, 32, 138, 180, 167, 153, 132, 8, 138, 2, 209, 4, 138, 1, 128, 138, 92, 136, 44, 129, 136, 162, 33, 63, 40, 141, 2, 160, 144, 106, 137, 64, 155, 17, 129, 60, 30, 146, 26, 17, 28, 48, 46, 169, 51, 154, 91, 137, 41, 26, 32, 143, 18, 138, + 1, 32, 28, 123, 177, 9, 181, 195, 56, 57, 14, 145, 161, 17, 17, 31, 41, 152, 145, 194, 194, 20, 153, 41, 9, 243, 129, 180, 0, 128, 45, 16, 43, 170, 135, 144, 16, 25, 42, 137, 242, 163, 194, 16, 0, 57, 14, 130, 194, 178, 16, 33, 30, 8, 59, 211, 163, 160, 5, 137, 44, 10, 17, 170, 3, 120, 9, 44, 146, 136, 131, 140, 91, 9, 171, + 7, 161, 32, 73, 13, 8, 161, 40, 106, 11, 25, 129, 59, 0, 49, 31, 42, 28, 40, 11, 0, 81, 176, 61, 32, 138, 25, 178, 241, 148, 136, 106, 8, 136, 128, 177, 90, 8, 155, 96, 176, 9, 18, 217, 132, 129, 10, 81, 156, 40, 178, 161, 36, 169, 76, 147, 203, 150, 0, 10, 146, 200, 147, 149, 128, 144, 148, 154, 182, 24, 0, 137, 11, 134, 211, + 24, 136, 129, 145, 209, 33, 8, 43, 163, 243, 88, 41, 13, 0, 160, 145, 33, 31, 32, 185, 145, 4, 155, 17, 32, 47, 161, 128, 73, 160, 44, 56, 176, 75, 74, 12, 35, 141, 104, 137, 9, 89, 152, 58, 56, 44, 41, 30, 41, 40, 157, 48, 128, 154, 88, 41, 42, 8, 14, 3, 184, 59, 120, 152, 9, 56, 10, 128, 41, 57, 227, 186, 52, 152, 62, 8, 56, + 242, 0, 58, 8, 156, 34, 243, 128, 24, 176, 51, 169, 58, 183, 192, 146, 164, 177, 18, 170, 7, 177, 208, 132, 161, 24, 136, 27, 147, 243, 128, 133, 10, 24, 161, 161, 178, 214, 17, 160, 25, 16, 161, 137, 165, 192, 48, 27, 72, 58, 218, 133, 162, 26, 72, 27, 10, 197, 178, 49, 138, 89, 56, 142, 1, 24, 11, 0, 44, 105, 10, 25, 0, + 194, 9, 3, 47, 8, 138, 147, 18, 28, 48, 202, 147, 199, 146, 25, 161, 0, 145, 194, 163, 57, 11, 146, 248, 130, 32, 57, 63, 154, 16, 48, 14, 128, 144, 209, 133, 26, 56, 154, 182, 162, 195, 18, 152, 44, 194, 180, 168, 5, 24, 137, 138, 35, 192, 232, 66, 176, 161, 24, 41, 26, 244, 129, 163, 160, 75, 129, 226, 147, 40, 145, 61, + 13, 130, 177, 17, 137, 112, 170, 130, 0, 136, 75, 152, 177, 241, 34, 0, 59, 156, 51, 186, 178, 91, 132, 137, 137, 122, 1, 45, 28, 50, 172, 57, 108, 8, 26, 136, 32, 152, 46, 144, 131, 171, 4, 152, 18, 141, 148, 1, 216, 32, 9, 60, 169, 66, 152, 128, 72, 90, 201, 1, 17, 201, 136, 3, 195, 26, 73, 133, 200, 176, 150, 146, 169, + 24, 33, 178, 184, 151, 73, 11, 28, 72, 44, 153, 82, 153, 17, 42, 57, 78, 153, 8, 160, 0, 1, 123, 11, 19, 171, 195, 18, 59, 31, 129, 10, 162, 2, 58, 96, 142, 130, 26, 75, 128, 176, 17, 180, 123, 9, 90, 137, 211, 145, 32, 26, 76, 43, 145, 130, 12, 90, 41, 27, 58, 160, 160, 128, 178, 7, 76, 59, 0, 203, 180, 147, 33, 62, 10, 0, 243, + 129, 146, 73, 29, 145, 144, 0, 26, 56, 153, 185, 83, 8, 76, 27, 166, 161, 193, 146, 131, 224, 145, 165, 161, 40, 168, 149, 162, 226, 2, 136, 138, 163, 131, 211, 0, 59, 146, 218, 148, 1, 192, 16, 16, 58, 248, 88, 144, 177, 136, 1, 58, 45, 9, 195, 197, 147, 48, 29, 10, 0, 162, 176, 64, 122, 9, 10, 17, 9, 153, 56, 75, 27, 31, + 72, 136, 9, 129, 129, 61, 45, 59, 10, 161, 18, 122, 43, 59, 41, 169, 34, 155, 130, 131, 219, 120, 162, 27, 49, 208, 160, 131, 156, 66, 12, 145, 50, 240, 16, 136, 12, 162, 40, 129, 130, 15, 129, 162, 146, 180, 83, 139, 58, 217, 129, 177, 4, 0, 169, 197, 163, 144, 242, 131, 168, 179, 179, 17, 197, 145, 178, 164, 128, 160, + 211, 2, 244, 163, 145, 162, 129, 212, 177, 163, 17, 208, 163, 195, 180, 57, 24, 170, 182, 164, 129, 0, 60, 60, 169, 149, 162, 177, 122, 26, 24, 136, 136, 133, 43, 27, 178, 56, 77, 24, 128, 240, 0, 2, 44, 46, 8, 128, 193, 146, 64, 27, 42, 16, 193, 25, 0, 192, 148, 11, 52, 47, 153, 147, 243, 0, 24, 73, 28, 144, 161, 150, 9, + 8, 73, 170, 2, 162, 25, 27, 147, 167, 131, 29, 1, 168, 200, 165, 16, 91, 137, 8, 162, 176, 35, 41, 31, 24, 169, 50, 168, 58, 123, 144, 48, 128, 13, 73, 169, 144, 16, 57, 123, 44, 200, 163, 56, 153, 80, 10, 176, 146, 57, 94, 8, 152, 131, 9, 168, 125, 26, 145, 177, 132, 137, 41, 60, 26, 144, 243, 32, 192, 34, 60, 43, 26, 16, + 249, 164, 16, 58, 61, 11, 130, 243, 146, 2, 42, 44, 27, 128, 165, 137, 49, 45, 28, 16, 43, 8, 211, 48, 28, 152, 105, 9, 9, 163, 161, 169, 35, 107, 42, 232, 164, 130, 168, 72, 42, 168, 210, 148, 144, 136, 129, 3, 217, 194, 50, 27, 192, 41, 210, 147, 40, 76, 226, 1, 161, 1, 155, 132, 145, 147, 171, 67, 173, 210, 132, 161, 106, + 137, 56, 169, 209, 131, 64, 13, 129, 9, 194, 17, 57, 61, 169, 17, 128, 40, 31, 16, 10, 162, 57, 61, 75, 139, 40, 242, 17, 58, 59, 138, 179, 144, 50, 105, 140, 179, 243, 57, 40, 26, 9, 243, 130, 24, 29, 57, 128, 210, 129, 25, 59, 91, 137, 162, 178, 72, 27, 181, 168, 19, 129, 8, 184, 231, 147, 178, 32, 28, 184, 198, 148, 144, + 1, 26, 128, 16, 192, 2, 26, 144, 244, 129, 0, 16, 10, 197, 177, 181, 1, 41, 9, 178, 165, 211, 129, 25, 145, 137, 210, 147, 152, 210, 163, 132, 194, 17, 91, 169, 145, 181, 130, 9, 89, 137, 152, 178, 4, 128, 9, 63, 160, 128, 106, 8, 25, 43, 10, 32, 47, 26, 123, 152, 24, 40, 25, 27, 18, 186, 35, 158, 64, 42, 216, 33, 25, 58, 58, + 45, 184, 147, 29, 72, 46, 9, 0, 178, 146, 58, 77, 26, 25, 209, 165, 128, 145, 17, 153, 128, 129, 148, 240, 129, 1, 40, 31, 0, 152, 242, 163, 16, 59, 44, 24, 243, 146, 128, 1, 26, 26, 179, 213, 145, 130, 176, 131, 40, 25, 145, 219, 179, 167, 8, 33, 59, 14, 176, 166, 16, 136, 74, 128, 176, 128, 149, 8, 8, 209, 148, 152, 0, 72, + 153, 161, 178, 35, 62, 75, 154, 163, 153, 19, 62, 170, 133, 179, 136, 89, 12, 129, 164, 144, 3, 47, 58, 193, 177, 148, 0, 61, 43, 10, 129, 17, 41, 61, 43, 25, 8, 126, 26, 25, 137, 145, 34, 44, 45, 129, 216, 179, 1, 90, 25, 137, 32, 227, 8, 16, 9, 170, 49, 31, 32, 29, 128, 145, 148, 75, 25, 75, 153, 162, 192, 35, 12, 80, 136, + 176, 8, 194, 24, 1, 176, 21, 154, 145, 80, 251, 130, 2, 30, 9, 8, 130, 145, 128, 98, 27, 26, 129, 136, 162, 15, 33, 168, 59, 65, 177, 77, 141, 1, 128, 168, 113, 10, 137, 178, 163, 146, 132, 74, 153, 224, 164, 33, 184, 19, 184, 228, 161, 17, 91, 152, 25, 146, 152, 44, 121, 9, 160, 145, 17, 25, 28, 93, 128, 152, 2, 25, 27, 161, + 210, 129, 146, 45, 179, 227, 163, 162, 9, 40, 193, 148, 179, 57, 107, 140, 196, 32, 25, 57, 47, 136, 210, 130, 24, 40, 28, 152, 210, 182, 145, 40, 8, 129, 184, 147, 147, 140, 163, 166, 160, 34, 45, 144, 194, 161, 134, 41, 46, 152, 162, 162, 3, 44, 58, 75, 209, 162, 144, 57, 129, 47, 152, 130, 59, 16, 248, 129, 17, 26, 57, + 9, 29, 167, 2, 60, 42, 138, 136, 209, 130, 90, 42, 42, 176, 146, 178, 120, 28, 8, 160, 145, 16, 33, 31, 1, 8, 160, 129, 128, 242, 164, 32, 152, 177, 146, 213, 196, 128, 40, 26, 160, 163, 180, 146, 108, 60, 144, 144, 136, 147, 137, 40, 90, 161, 3, 17, 219, 243, 33, 184, 130, 60, 136, 243, 178, 179, 132, 26, 8, 168, 212, 147, + 16, 57, 42, 31, 145, 145, 160, 32, 43, 184, 66, 45, 180, 33, 140, 226, 1, 91, 152, 16, 144, 193, 162, 48, 77, 25, 137, 153, 17, 178, 78, 0, 0, 16, 14, 90, 152, 153, 19, 129, 13, 123, 137, 129, 160, 1, 73, 44, 9, 129, 0, 153, 120, 10, 9, 162, 195, 32, 139, 28, 151, 161, 2, 128, 26, 45, 193, 146, 48, 29, 146, 153, 194, 5, 59, + 29, 128, 144, 195, 1, 64, 43, 208, 178, 149, 8, 9, 16, 240, 163, 129, 16, 42, 185, 181, 211, 24, 48, 45, 137, 149, 9, 24, 41, 75, 184, 177, 4, 43, 91, 128, 180, 16, 144, 29, 25, 184, 167, 1, 59, 60, 153, 148, 161, 146, 91, 42, 186, 4, 24, 145, 123, 11, 2, 178, 77, 136, 26, 25, 195, 40, 115, 61, 27, 168, 177, 3, 59, 79, 26, 25, + 144, 1, 48, 13, 56, 154, 248, 1, 16, 9, 129, 8, 2, 178, 31, 130, 153, 162, 20, 15, 33, 170, 56, 40, 29, 28, 128, 152, 149, 144, 56, 120, 11, 162, 212, 129, 144, 145, 59, 180, 243, 147, 145, 144, 16, 152, 48, 241, 0, 161, 176, 1, 134, 10, 129, 200, 166, 144, 128, 121, 26, 24, 177, 178, 196, 48, 75, 138, 41, 180, 195, 26, 24, + 89, 138, 24, 33, 187, 41, 84, 155, 57, 79, 136, 160, 210, 130, 0, 58, 58, 168, 243, 132, 27, 41, 75, 138, 3, 8, 61, 8, 29, 145, 179, 76, 24, 28, 146, 208, 2, 49, 140, 75, 196, 144, 0, 40, 44, 179, 208, 3, 176, 33, 15, 177, 2, 160, 106, 8, 160, 164, 164, 8, 73, 27, 226, 179, 161, 1, 57, 1, 196, 211, 128, 40, 156, 145, 166, 178, + 131, 29, 128, 145, 162, 165, 40, 27, 216, 146, 135, 144, 40, 160, 194, 177, 145, 20, 139, 200, 151, 178, 17, 136, 40, 25, 205, 130, 17, 11, 17, 129, 156, 38, 26, 25, 137, 179, 163, 11, 79, 16, 12, 146, 147, 143, 89, 25, 136, 136, 25, 48, 26, 46, 129, 40, 29, 42, 29, 8, 145, 2, 56, 27, 62, 8, 25, 212, 161, 48, 43, 144, 129, + 29, 145, 144, 41, 106, 10, 107, 43, 184, 131, 1, 36, 61, 13, 138, 2, 194, 1, 16, 27, 75, 186, 181, 151, 8, 1, 161, 138, 211, 129, 2, 59, 248, 129, 16, 0, 144, 63, 152, 150, 136, 24, 25, 128, 30, 161, 128, 17, 24, 225, 146, 10, 16, 0, 9, 227, 183, 129, 40, 60, 26, 162, 194, 181, 24, 90, 9, 24, 0, 176, 161, 193, 194, 35, 12, 63, + 8, 210, 162, 1, 32, 78, 28, 152, 164, 144, 16, 48, 45, 137, 162, 147, 168, 152, 98, 27, 43, 33, 12, 160, 165, 129, 137, 63, 41, 153, 153, 151, 16, 91, 26, 8, 8, 9, 56, 10, 46, 24, 146, 57, 168, 160, 166, 241, 129, 32, 140, 16, 145, 179, 164, 137, 113, 138, 208, 131, 26, 25, 1, 42, 178, 196, 106, 24, 171, 18, 196, 8, 18, 29, + 41, 194, 128, 3, 249, 57, 162, 152, 48, 184, 120, 160, 208, 33, 137, 74, 57, 187, 149, 129, 26, 35, 158, 72, 128, 168, 32, 26, 25, 180, 75, 2, 136, 15, 163, 161, 136, 120, 27, 41, 160, 128, 182, 56, 60, 25, 12, 178, 151, 128, 168, 72, 10, 152, 4, 177, 26, 147, 137, 113, 44, 42, 33, 220, 2, 152, 41, 82, 11, 210, 163, 184, + 133, 162, 10, 196, 128, 3, 234, 40, 149, 152, 161, 1, 44, 129, 194, 4, 225, 16, 58, 168, 24, 194, 146, 146, 154, 49, 21, 218, 33, 152, 248, 129, 194, 147, 0, 28, 1, 195, 162, 20, 140, 42, 25, 160, 198, 1, 33, 136, 142, 3, 25, 24, 141, 16, 177, 208, 112, 0, 138, 41, 160, 130, 45, 60, 32, 170, 73, 24, 75, 59, 161, 176, 49, 159, + 97, 26, 168, 149, 145, 32, 28, 25, 184, 211, 129, 179, 74, 73, 8, 153, 136, 193, 151, 160, 32, 48, 143, 9, 147, 181, 145, 32, 60, 9, 187, 133, 166, 144, 32, 152, 25, 136, 161, 150, 168, 145, 81, 10, 42, 0, 169, 182, 148, 136, 58, 41, 187, 182, 211, 131, 16, 137, 25, 243, 144, 129, 2, 9, 8, 202, 7, 25, 185, 21, 144, 136, 153, + 65, 184, 137, 56, 151, 10, 153, 49, 16, 145, 14, 56, 176, 11, 192, 19, 89, 91, 44, 168, 147, 2, 8, 147, 63, 27, 1, 136, 229, 129, 73, 26, 136, 26, 137, 81, 170, 147, 77, 72, 12, 42, 42, 192, 24, 104, 91, 26, 27, 65, 177, 27, 32, 41, 60, 14, 136, 17, 170, 150, 129, 24, 58, 11, 16, 251, 162, 19, 57, 31, 0, 152, 129, 145, 17, 61, + 14, 1, 129, 27, 129, 66, 169, 178, 74, 12, 11, 19, 198, 145, 75, 33, 138, 174, 133, 1, 184, 57, 40, 136, 169, 20, 1, 60, 174, 20, 154, 201, 67, 26, 162, 151, 42, 16, 138, 59, 130, 204, 20, 169, 59, 180, 59, 114, 184, 56, 178, 242, 128, 130, 43, 8, 194, 3, 229, 144, 33, 185, 144, 34, 181, 145, 168, 17, 149, 153, 74, 35, 220, + 129, 128, 1, 88, 59, 75, 225, 136, 130, 168, 17, 144, 12, 151, 8, 25, 179, 8, 1, 240, 16, 8, 25, 145, 211, 41, 130, 138, 115, 169, 160, 163, 168, 84, 154, 74, 0, 170, 144, 211, 149, 2, 30, 128, 137, 9, 149, 1, 144, 58, 60, 57, 153, 178, 150, 17, 29, 27, 74, 25, 195, 152, 56, 15, 1, 25, 26, 152, 149, 80, 153, 57, 73, 140, 128, + 160, 144, 113, 27, 56, 28, 25, 4, 42, 44, 137, 60, 171, 130, 50, 240, 8, 5, 139, 145, 1, 105, 137, 200, 80, 137, 145, 146, 178, 179, 160, 46, 16, 240, 195, 131, 128, 144, 24, 164, 198, 128, 0, 136, 137, 131, 194, 165, 177, 2, 161, 147, 11, 144, 188, 181, 148, 144, 23, 0, 28, 224, 128, 131, 192, 32, 1, 224, 1, 168, 132, 145, + 9, 41, 208, 58, 137, 179, 151, 145, 16, 1, 30, 8, 145, 178, 1, 47, 32, 186, 72, 169, 146, 75, 8, 41, 48, 136, 89, 13, 48, 9, 10, 124, 26, 11, 42, 32, 129, 91, 77, 16, 12, 128, 42, 57, 138, 10, 60, 2, 63, 9, 0, 93, 128, 152, 90, 8, 10, 24, 40, 44, 144, 29, 49, 188, 48, 72, 25, 30, 177, 33, 128, 186, 120, 129, 186, 133, 152, 130, + 24, 156, 51, 154, 8, 226, 2, 56, 155, 2, 179, 233, 167, 128, 24, 129, 176, 136, 151, 8, 184, 0, 33, 224, 152, 21, 177, 24, 10, 163, 16, 250, 17, 130, 171, 83, 137, 136, 37, 12, 56, 242, 154, 17, 160, 145, 82, 13, 3, 201, 128, 18, 137, 24, 162, 63, 162, 8, 107, 178, 128, 57, 158, 32, 24, 200, 18, 0, 106, 154, 73, 16, 248, 8, + 73, 137, 57, 75, 0, 128, 12, 65, 137, 59, 75, 28, 144, 129, 122, 0, 58, 140, 160, 195, 145, 105, 56, 28, 153, 145, 164, 88, 8, 28, 25, 153, 9, 162, 113, 89, 153, 136, 33, 234, 147, 128, 41, 72, 11, 138, 151, 144, 145, 16, 43, 58, 248, 130, 178, 42, 4, 40, 10, 196, 154, 147, 216, 24, 7, 136, 10, 161, 148, 210, 161, 98, 138, + 137, 128, 146, 176, 33, 105, 27, 43, 163, 49, 185, 6, 10, 136, 43, 67, 174, 161, 162, 151, 137, 1, 64, 200, 193, 24, 64, 200, 56, 145, 242, 24, 57, 137, 1, 128, 3, 162, 175, 80, 128, 162, 152, 25, 58, 175, 17, 17, 0, 200, 64, 168, 162, 91, 1, 154, 44, 211, 177, 35, 64, 160, 161, 144, 4, 241, 41, 209, 162, 25, 1, 3, 242, 176, + 134, 153, 42, 41, 136, 135, 154, 2, 130, 46, 41, 161, 153, 180, 145, 34, 26, 46, 18, 242, 137, 146, 129, 25, 128, 11, 151, 161, 40, 179, 27, 122, 168, 59, 137, 181, 50, 172, 36, 56, 15, 9, 129, 137, 128, 75, 2, 58, 12, 52, 141, 8, 24, 58, 153, 157, 122, 145, 9, 1, 80, 27, 184, 32, 74, 219, 50, 57, 168, 153, 180, 48, 28, 143, + 131, 144, 178, 65, 13, 48, 168, 162, 147, 155, 121, 9, 170, 5, 16, 153, 21, 29, 144, 161, 91, 0, 184, 57, 128, 137, 17, 159, 88, 178, 128, 105, 152, 9, 162, 33, 164, 141, 88, 178, 224, 1, 0, 16, 27, 185, 150, 161, 9, 4, 139, 16, 128, 160, 194, 144, 65, 180, 46, 40, 136, 27, 135, 160, 16, 44, 57, 145, 236, 2, 195, 40, 75, 177, + 2, 200, 179, 146, 186, 104, 50, 141, 24, 169, 165, 148, 11, 97, 10, 11, 130, 177, 49, 57, 78, 42, 154, 128, 165, 59, 33, 28, 30, 1, 136, 16, 192, 41, 128, 152, 123, 136, 24, 1, 169, 113, 10, 11, 49, 153, 14, 147, 19, 45, 43, 8, 176, 210, 148, 8, 16, 11, 96, 144, 192, 163, 150, 10, 128, 43, 26, 150, 178, 165, 24, 41, 171, 18, + 27, 215, 1, 8, 128, 136, 40, 35, 208, 11, 161, 193, 18, 73, 154, 133, 155, 165, 164, 10, 49, 154, 8, 199, 0, 2, 168, 64, 192, 0, 40, 162, 43, 202, 180, 150, 10, 106, 24, 185, 145, 131, 184, 113, 43, 24, 162, 187, 73, 146, 42, 81, 171, 121, 58, 155, 151, 16, 43, 32, 31, 9, 160, 146, 17, 136, 94, 10, 24, 145, 25, 9, 130, 59, + 65, 13, 91, 25, 169, 146, 176, 112, 42, 59, 16, 217, 130, 20, 13, 25, 9, 40, 161, 138, 68, 169, 154, 18, 62, 154, 180, 145, 135, 152, 56, 58, 155, 165, 211, 8, 40, 42, 10, 198, 1, 2, 184, 57, 184, 224, 51, 154, 27, 134, 168, 19, 202, 73, 75, 184, 35, 176, 75, 24, 25, 209, 51, 157, 19, 30, 184, 179, 3, 33, 148, 45, 232, 146, + 129, 168, 41, 32, 170, 149, 193, 35, 136, 16, 50, 191, 56, 146, 173, 149, 16, 24, 41, 30, 129, 168, 209, 3, 57, 31, 0, 16, 176, 147, 41, 152, 10, 17, 181, 14, 40, 144, 49, 170, 75, 97, 141, 25, 162, 146, 72, 177, 92, 137, 137, 19, 137, 153, 113, 154, 2, 41, 60, 129, 217, 2, 211, 152, 73, 42, 193, 197, 146, 147, 10, 59, 0, + 192, 196, 132, 41, 160, 25, 88, 169, 16, 40, 241, 1, 153, 81, 28, 10, 147, 161, 209, 88, 75, 9, 161, 162, 180, 16, 43, 57, 235, 33, 56, 156, 129, 144, 2, 135, 31, 128, 145, 136, 163, 56, 59, 154, 57, 167, 160, 105, 137, 0, 138, 163, 3, 41, 47, 185, 211, 131, 41, 41, 60, 139, 182, 146, 16, 16, 43, 242, 144, 145, 129, 16, 179, + 183, 1, 26, 9, 147, 240, 131, 160, 91, 74, 152, 184, 166, 178, 33, 140, 9, 4, 162, 233, 34, 136, 129, 144, 163, 60, 142, 144, 149, 128, 33, 73, 13, 161, 194, 131, 0, 26, 56, 142, 128, 163, 128, 1, 233, 56, 209, 41, 145, 194, 147, 179, 149, 64, 30, 8, 128, 216, 18, 24, 43, 43, 32, 153, 25, 74, 109, 137, 153, 48, 8, 137, 122, + 25, 144, 26, 43, 59, 30, 33, 41, 27, 24, 96, 153, 160, 50, 76, 27, 47, 152, 145, 163, 73, 40, 14, 152, 131, 176, 74, 90, 8, 8, 200, 67, 155, 154, 50, 49, 155, 28, 124, 177, 152, 1, 2, 17, 62, 138, 180, 176, 4, 25, 9, 177, 245, 162, 129, 40, 25, 176, 164, 130, 172, 4, 8, 181, 194, 49, 11, 168, 154, 165, 133, 152, 40, 136, 226, + 179, 19, 26, 185, 16, 167, 194, 16, 25, 57, 243, 136, 147, 1, 31, 25, 184, 132, 160, 33, 62, 138, 129, 130, 41, 121, 137, 153, 145, 26, 17, 107, 136, 179, 1, 61, 60, 26, 162, 168, 148, 64, 31, 25, 32, 168, 152, 64, 31, 137, 8, 129, 33, 62, 24, 137, 8, 16, 59, 47, 153, 33, 162, 91, 59, 41, 170, 145, 5, 43, 60, 41, 13, 178, 134, + 57, 153, 12, 194, 227, 8, 2, 128, 57, 208, 162, 19, 216, 32, 178, 25, 128, 160, 48, 194, 195, 37, 155, 10, 33, 251, 163, 146, 16, 136, 12, 166, 195, 160, 148, 129, 176, 147, 178, 150, 160, 72, 162, 162, 193, 162, 60, 200, 145, 5, 144, 25, 122, 216, 129, 161, 130, 0, 10, 73, 1, 241, 2, 9, 168, 33, 13, 161, 165, 24, 64, 203, + 50, 1, 14, 9, 9, 129, 161, 106, 33, 27, 13, 164, 128, 40, 41, 107, 169, 160, 33, 136, 60, 92, 168, 152, 2, 91, 57, 176, 129, 0, 144, 47, 136, 162, 164, 128, 80, 43, 154, 179, 213, 130, 74, 27, 0, 145, 145, 167, 58, 59, 160, 9, 26, 76, 8, 171, 5, 49, 28, 44, 169, 162, 183, 130, 72, 28, 144, 179, 228, 2, 25, 26, 129, 186, 151, + 1, 75, 128, 169, 17, 178, 15, 57, 170, 16, 166, 16, 57, 8, 139, 162, 181, 1, 8, 152, 164, 181, 41, 81, 43, 10, 242, 145, 57, 139, 89, 8, 193, 18, 154, 32, 176, 10, 165, 129, 137, 147, 177, 134, 0, 25, 25, 201, 147, 227, 129, 72, 59, 185, 167, 128, 129, 160, 91, 25, 176, 130, 147, 145, 9, 160, 5, 202, 17, 16, 186, 136, 37, + 177, 56, 76, 42, 169, 186, 48, 9, 145, 57, 24, 128, 41, 169, 134, 137, 145, 147, 28, 41, 168, 131, 228, 32, 27, 9, 60, 129, 178, 64, 60, 45, 25, 9, 24, 152, 49, 31, 136, 57, 42, 0, 25, 12, 181, 18, 153, 57, 96, 169, 177, 132, 153, 123, 9, 152, 129, 177, 17, 74, 43, 24, 169, 128, 121, 137, 25, 1, 139, 96, 42, 10, 146, 178, 18, + 44, 29, 1, 161, 164, 146, 31, 137, 146, 177, 19, 1, 10, 26, 209, 165, 146, 43, 40, 138, 240, 130, 18, 144, 25, 40, 212, 1, 58, 11, 152, 196, 147, 10, 74, 26, 152, 225, 130, 146, 58, 60, 210, 145, 16, 148, 16, 185, 192, 18, 44, 42, 57, 199, 162, 1, 9, 87, 47, 186, 215, 231, 197, 179, 180, 195, 212, 164, 32, 59, 92, 126, 62, + 41, 59, 76, 59, 60, 168, 179, 213, 197, 163, 72, 44, 25, 74, 126, 127, 127, 79, 26, 177, 148, 90, 27, 225, 247, 165, 0, 152, 147, 123, 138, 211, 164, 72, 126, 127, 46, 210, 196, 163, 228, 215, 64, 11, 210, 180, 1, 8, 58, 153, 1, 224, 149, 57, 76, 27, 24, 76, 42, 43, 136, 128, 243, 179, 130, 106, 60, 42, 42, 92, 28, 243, 231, + 147, 24, 57, 44, 58, 94, 45, 8, 57, 139, 214, 148, 40, 77, 26, 9, 16, 10, 144, 64, 62, 43, 25, 123, 59, 138, 162, 48, 63, 26, 41, 92, 60, 43, 176, 3, 59, 232, 214, 164, 16, 75, 75, 76, 60, 153, 179, 33, 62, 26, 136, 40, 75, 169, 197, 163, 129, 57, 60, 59, 75, 138, 145, 64, 63, 138, 179, 1, 42, 136, 90, 43, 176, 214, 180, 1, 25, + 152, 195, 129, 129, 106, 76, 60, 137, 145, 178, 2, 25, 10, 228, 130, 57, 59, 44, 41, 154, 165, 105, 76, 44, 144, 16, 76, 26, 41, 76, 26, 152, 1, 58, 26, 9, 193, 165, 16, 92, 26, 41, 77, 59, 76, 76, 60, 26, 136, 161, 130, 152, 195, 163, 211, 146, 0, 57, 11, 211, 130, 8, 25, 40, 62, 153, 162, 17, 109, 60, 153, 146, 40, 76, 60, + 26, 160, 179, 211, 163, 32, 60, 42, 153, 179, 194, 199, 130, 24, 58, 43, 58, 27, 128, 161, 195, 129, 226, 196, 147, 90, 59, 75, 44, 136, 128, 145, 160, 148, 123, 59, 42, 26, 41, 26, 57, 27, 192, 215, 147, 57, 59, 27, 161, 145, 213, 130, 106, 76, 43, 9, 144, 162, 129, 177, 181, 130, 136, 194, 146, 40, 10, 129, 25, 210, 146, + 178, 197, 196, 179, 196, 130, 8, 41, 9, 144, 178, 130, 209, 182, 17, 92, 43, 176, 147, 144, 212, 130, 136, 0, 177, 130, 73, 62, 10, 161, 130, 91, 75, 59, 43, 57, 46, 25, 41, 77, 10, 177, 164, 16, 26, 136, 210, 197, 179, 130, 128, 57, 77, 43, 25, 75, 10, 227, 179, 180, 179, 146, 128, 57, 185, 183, 163, 145, 0, 8, 8, 10, 119, + 114, 120, 16, 210, 244, 60, 28, 41, 25, 152, 149, 56, 161, 35, 44, 89, 27, 24, 136, 24, 164, 211, 17, 233, 176, 136, 192, 129, 179, 17, 17, 25, 0, 10, 46, 160, 132, 49, 66, 24, 132, 177, 147, 193, 56, 72, 26, 29, 232, 168, 176, 12, 137, 41, 139, 147, 9, 1, 41, 15, 91, 136, 35, 148, 21, 18, 48, 40, 1, 168, 167, 144, 0, 42, 172, + 177, 204, 193, 155, 232, 152, 152, 26, 152, 41, 146, 17, 6, 4, 65, 34, 35, 135, 4, 16, 32, 9, 24, 186, 176, 0, 250, 153, 204, 186, 173, 154, 153, 177, 3, 65, 41, 34, 145, 134, 35, 65, 98, 49, 50, 50, 2, 33, 169, 138, 155, 175, 170, 172, 204, 192, 138, 234, 136, 155, 136, 10, 32, 18, 5, 52, 48, 24, 162, 17, 67, 54, 66, 51, 34, + 131, 184, 174, 234, 153, 10, 9, 40, 0, 152, 251, 168, 142, 154, 9, 16, 33, 49, 33, 128, 154, 170, 156, 34, 54, 54, 33, 68, 0, 1, 136, 201, 137, 26, 88, 48, 35, 99, 8, 152, 189, 189, 187, 155, 171, 16, 24, 130, 145, 188, 175, 203, 144, 49, 115, 67, 67, 50, 19, 2, 1, 0, 0, 130, 131, 1, 136, 206, 216, 188, 203, 204, 187, 187, + 156, 153, 0, 0, 51, 17, 34, 24, 112, 20, 69, 67, 67, 34, 19, 0, 136, 169, 185, 137, 186, 232, 185, 219, 201, 203, 187, 173, 170, 154, 153, 129, 131, 6, 2, 19, 49, 49, 21, 65, 19, 53, 51, 83, 34, 16, 168, 201, 154, 172, 156, 138, 0, 1, 24, 201, 233, 186, 204, 186, 171, 137, 3, 37, 48, 24, 128, 201, 202, 202, 129, 17, 48, 21, + 22, 20, 19, 19, 32, 16, 2, 66, 52, 68, 4, 3, 1, 203, 235, 188, 189, 186, 171, 153, 137, 153, 170, 219, 170, 140, 9, 17, 53, 115, 50, 52, 67, 51, 51, 51, 17, 130, 0, 145, 154, 169, 188, 236, 187, 190, 203, 187, 172, 171, 138, 136, 17, 33, 18, 2, 34, 98, 98, 50, 50, 52, 66, 34, 35, 2, 19, 24, 169, 203, 203, 188, 219, 169, 154, + 9, 137, 171, 204, 188, 203, 184, 136, 34, 83, 50, 33, 153, 184, 170, 170, 152, 40, 57, 19, 36, 50, 50, 18, 35, 17, 2, 49, 49, 66, 66, 66, 34, 17, 168, 233, 202, 202, 170, 171, 170, 186, 219, 203, 188, 188, 154, 138, 25, 33, 68, 52, 68, 67, 67, 36, 51, 36, 18, 17, 17, 136, 8, 170, 176, 202, 188, 206, 202, 171, 172, 186, 169, + 153, 8, 25, 144, 128, 1, 34, 68, 52, 68, 51, 52, 34, 49, 18, 34, 2, 144, 136, 155, 140, 187, 186, 186, 154, 154, 185, 185, 153, 9, 9, 0, 24, 0, 128, 144, 168, 169, 170, 154, 154, 153, 9, 8, 16, 8, 0, 144, 19, 35, 68, 51, 52, 67, 51, 66, 34, 50, 33, 1, 144, 185, 186, 172, 204, 187, 188, 173, 172, 186, 172, 186, 154, 138, 41, + 33, 52, 53, 83, 50, 51, 52, 52, 37, 34, 34, 18, 16, 144, 152, 154, 187, 219, 203, 188, 173, 186, 186, 186, 170, 154, 153, 138, 144, 16, 17, 67, 82, 50, 51, 21, 34, 19, 33, 2, 18, 33, 1, 8, 153, 169, 153, 153, 136, 128, 0, 136, 154, 153, 153, 8, 8, 1, 16, 0, 169, 170, 187, 171, 171, 154, 153, 153, 152, 153, 153, 0, 16, 51, 83, + 66, 50, 67, 50, 51, 67, 51, 52, 35, 18, 136, 186, 219, 187, 189, 186, 171, 187, 173, 187, 188, 187, 203, 138, 9, 16, 33, 50, 52, 53, 67, 67, 147, 8, 128, 128, 128, 128, 128, 128, 128, 128, 0, 240, 255, 55, 232, 23, 220, 0, 148, 1, 9, 18, 148, 10, 189, 32, 163, 62, 160, 5, 137, 12, 149, 42, 153, 144, 34, 42, 8, 1, 138, 181, + 45, 136, 18, 144, 105, 138, 1, 160, 14, 128, 132, 145, 186, 37, 138, 41, 192, 48, 145, 46, 160, 33, 44, 24, 225, 16, 13, 132, 136, 137, 16, 148, 25, 170, 194, 82, 152, 136, 91, 24, 42, 169, 33, 233, 131, 179, 24, 185, 149, 16, 57, 172, 164, 18, 10, 211, 160, 147, 211, 33, 138, 243, 129, 16, 41, 193, 0, 43, 132, 155, 73, + 58, 145, 244, 145, 43, 35, 9, 171, 16, 110, 25, 8, 28, 74, 162, 128, 26, 27, 82, 45, 136, 153, 18, 8, 136, 8 +}; +#endif // DISABLE_PC98_RHYTHM_CHANNEL + +TownsPC98_FmSynth::ChanInternal::ChanInternal() { + memset(this, 0, sizeof(ChanInternal)); +} + +TownsPC98_FmSynth::ChanInternal::~ChanInternal() { + for (uint i = 0; i < ARRAYSIZE(opr); ++i) + delete opr[i]; +} diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h new file mode 100644 index 0000000000..18cca56e29 --- /dev/null +++ b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h @@ -0,0 +1,196 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TOWNS_PC98_FMSYNTH_H +#define TOWNS_PC98_FMSYNTH_H + +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "common/list.h" + +#ifdef __DS__ +/* This disables the rhythm channel when emulating the PC-98 type 86 sound card. + * The only purpose is code size reduction for certain backends. + * At the moment the only games which make use of the rhythm channel are the + * (very rare) PC-98 versions of Legend of Kyrandia 2 and Lands of Lore. Music will + * still be okay, just missing a couple of rhythm instruments. + */ +#define DISABLE_PC98_RHYTHM_CHANNEL +#endif + +class TownsPC98_FmSynthOperator; +class TownsPC98_FmSynthSquareSineSource; +#ifndef DISABLE_PC98_RHYTHM_CHANNEL +class TownsPC98_FmSynthPercussionSource; +#endif + +enum EnvelopeState { + kEnvReady, + kEnvAttacking, + kEnvDecaying, + kEnvSustaining, + kEnvReleasing +}; + +class TownsPC98_FmSynth : public Audio::AudioStream { +public: + enum EmuType { + kTypeTowns, + kType26, + kType86 + }; + + TownsPC98_FmSynth(Audio::Mixer *mixer, EmuType type); + virtual ~TownsPC98_FmSynth(); + + virtual bool init(); + virtual void reset(); + + void writeReg(uint8 part, uint8 regAddress, uint8 value); + + // AudioStream interface + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { + return true; + } + bool endOfData() const { + return false; + } + int getRate() const { + return _mixer->getOutputRate(); + } + +protected: + void deinit(); + + // Implement this in your inherited class if your driver generates + // additional output that has to be inserted into the buffer. + virtual void nextTickEx(int32 *buffer, uint32 bufferSize) {} + + void toggleRegProtection(bool prot) { + _regProtectionFlag = prot; + } + uint8 readSSGStatus(); + + virtual void timerCallbackA() = 0; + virtual void timerCallbackB() = 0; + + // The audio driver can store and apply two different audio settings + // (usually for music and sound effects). The channel mask will determine + // which channels get effected by the setting. The first bits will be + // the normal fm channels, the next bits the ssg channels and the final + // bit the rhythm channel. + void setVolumeIntern(int volA, int volB); + void setVolumeChannelMasks(int channelMaskA, int channelMaskB); + + const int _numChan; + const int _numSSG; + const bool _hasPercussion; + + Common::Mutex _mutex; +private: + void generateTables(); + void nextTick(int32 *buffer, uint32 bufferSize); + void generateOutput(int32 &leftSample, int32 &rightSample, int32 *del, int32 *feed); + + struct ChanInternal { + ChanInternal(); + ~ChanInternal(); + + void ampModSensitivity(uint32 value) { + ampModSvty = (1 << (3 - value)) - (((value >> 1) & 1) | (value & 1)); + } + void frqModSensitivity(uint32 value) { + frqModSvty = value << 5; + } + + uint16 frqTemp; + bool enableLeft; + bool enableRight; + bool updateEnvelopeParameters; + int32 feedbuf[3]; + uint8 algorithm; + + uint32 ampModSvty; + uint32 frqModSvty; + + TownsPC98_FmSynthOperator *opr[4]; + }; + + TownsPC98_FmSynthSquareSineSource *_ssg; +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + TownsPC98_FmSynthPercussionSource *_prc; +#endif + ChanInternal *_chanInternal; + + uint8 *_oprRates; + uint8 *_oprRateshift; + uint8 *_oprAttackDecay; + uint32 *_oprFrq; + uint32 *_oprSinTbl; + int32 *_oprLevelOut; + int32 *_oprDetune; + + bool _regProtectionFlag; + + typedef void (TownsPC98_FmSynth::*ChipTimerProc)(); + void idleTimerCallback() {} + + struct ChipTimer { + bool enabled; + uint16 value; + + int32 smpTillCb; + uint32 smpTillCbRem; + int32 smpPerCb; + uint32 smpPerCbRem; + + ChipTimerProc cb; + }; + + ChipTimer _timers[2]; + + int _volMaskA, _volMaskB; + uint16 _volumeA, _volumeB; + + const float _baserate; + uint32 _timerbase; + uint32 _rtt; + + Audio::Mixer *_mixer; + Audio::SoundHandle _soundHandle; + +#ifndef DISABLE_PC98_RHYTHM_CHANNEL + static const uint8 _percussionData[]; +#endif + static const uint32 _adtStat[]; + static const uint8 _detSrc[]; + static const int _ssgTables[]; + + bool _ready; +}; + +#endif + diff --git a/audio/softsynth/mt32.cpp b/audio/softsynth/mt32.cpp new file mode 100644 index 0000000000..3a3958d494 --- /dev/null +++ b/audio/softsynth/mt32.cpp @@ -0,0 +1,573 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "common/scummsys.h" + +#ifdef USE_MT32EMU + +#include "audio/softsynth/mt32/mt32emu.h" + +#include "audio/softsynth/emumidi.h" +#include "audio/musicplugin.h" +#include "audio/mpu401.h" + +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/events.h" +#include "common/file.h" +#include "common/system.h" +#include "common/util.h" +#include "common/archive.h" +#include "common/translation.h" + +#include "graphics/fontman.h" +#include "graphics/surface.h" + +class MidiChannel_MT32 : public MidiChannel_MPU401 { + void effectLevel(byte value) { } + void chorusLevel(byte value) { } +}; + +class MidiDriver_MT32 : public MidiDriver_Emulated { +private: + Audio::SoundHandle _handle; + MidiChannel_MT32 _midiChannels[16]; + uint16 _channelMask; + MT32Emu::Synth *_synth; + + int _outputRate; + +protected: + void generateSamples(int16 *buf, int len); + +public: + bool _initialising; + + MidiDriver_MT32(Audio::Mixer *mixer); + virtual ~MidiDriver_MT32(); + + int open(); + void close(); + void send(uint32 b); + void setPitchBendRange (byte channel, uint range); + void sysEx(const byte *msg, uint16 length); + + uint32 property(int prop, uint32 param); + MidiChannel *allocateChannel(); + MidiChannel *getPercussionChannel(); + + // AudioStream API + bool isStereo() const { return true; } + int getRate() const { return _outputRate; } +}; + +class MT32File : public MT32Emu::File { + Common::File _in; + Common::DumpFile _out; +public: + bool open(const char *filename, OpenMode mode) { + if (mode == OpenMode_read) + return _in.open(filename); + else + return _out.open(filename); + } + void close() { + _in.close(); + _out.close(); + } + size_t read(void *in, size_t size) { + return _in.read(in, size); + } + bool readBit8u(MT32Emu::Bit8u *in) { + byte b = _in.readByte(); + if (_in.eos()) + return false; + *in = b; + return true; + } + size_t write(const void *in, size_t size) { + return _out.write(in, size); + } + bool writeBit8u(MT32Emu::Bit8u out) { + _out.writeByte(out); + return !_out.err(); + } + bool isEOF() { + return _in.isOpen() && _in.eos(); + } +}; + +static int eatSystemEvents() { + Common::Event event; + Common::EventManager *eventMan = g_system->getEventManager(); + while (eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_QUIT: + return 1; + default: + break; + } + } + return 0; +} + +static void drawProgress(float progress) { + const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kOSDFont)); + Graphics::Surface *screen = g_system->lockScreen(); + + assert(screen); + assert(screen->pixels); + + Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); + + int16 w = g_system->getWidth() / 7 * 5; + int16 h = font.getFontHeight(); + int16 x = g_system->getWidth() / 7; + int16 y = g_system->getHeight() / 2 - h / 2; + + Common::Rect r(x, y, x + w, y + h); + + uint32 col; + + if (screenFormat.bytesPerPixel > 1) + col = screenFormat.RGBToColor(0, 171, 0); + else + col = 1; + + screen->frameRect(r, col); + + r.grow(-1); + r.setWidth(uint16(progress * w)); + + if (screenFormat.bytesPerPixel > 1) + col = screenFormat.RGBToColor(171, 0, 0); + else + col = 2; + + screen->fillRect(r, col); + + g_system->unlockScreen(); + g_system->updateScreen(); +} + +static void drawMessage(int offset, const Common::String &text) { + const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kOSDFont)); + Graphics::Surface *screen = g_system->lockScreen(); + + assert(screen); + assert(screen->pixels); + + Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); + + uint16 h = font.getFontHeight(); + uint16 y = g_system->getHeight() / 2 - h / 2 + offset * (h + 1); + + uint32 col; + + if (screenFormat.bytesPerPixel > 1) + col = screenFormat.RGBToColor(0, 0, 0); + else + col = 0; + + Common::Rect r(0, y, screen->w, y + h); + screen->fillRect(r, col); + + if (screenFormat.bytesPerPixel > 1) + col = screenFormat.RGBToColor(0, 171, 0); + else + col = 1; + + font.drawString(screen, text, 0, y, screen->w, col, Graphics::kTextAlignCenter); + + g_system->unlockScreen(); + g_system->updateScreen(); +} + +static MT32Emu::File *MT32_OpenFile(void *userData, const char *filename, MT32Emu::File::OpenMode mode) { + MT32File *file = new MT32File(); + if (!file->open(filename, mode)) { + delete file; + return NULL; + } + return file; +} + +static void MT32_PrintDebug(void *userData, const char *fmt, va_list list) { + if (((MidiDriver_MT32 *)userData)->_initialising) { + char buf[512]; + + vsnprintf(buf, 512, fmt, list); + buf[70] = 0; // Truncate to a reasonable length + + drawMessage(1, buf); + } + + //vdebug(0, fmt, list); // FIXME: Use a higher debug level +} + +static int MT32_Report(void *userData, MT32Emu::ReportType type, const void *reportData) { + switch (type) { + case MT32Emu::ReportType_lcdMessage: + g_system->displayMessageOnOSD((const char *)reportData); + break; + case MT32Emu::ReportType_errorControlROM: + error("Failed to load MT32_CONTROL.ROM"); + break; + case MT32Emu::ReportType_errorPCMROM: + error("Failed to load MT32_PCM.ROM"); + break; + case MT32Emu::ReportType_progressInit: + if (((MidiDriver_MT32 *)userData)->_initialising) { + drawProgress(*((const float *)reportData)); + return eatSystemEvents(); + } + break; + case MT32Emu::ReportType_availableSSE: + debug(1, "MT32emu: SSE is available"); + break; + case MT32Emu::ReportType_usingSSE: + debug(1, "MT32emu: using SSE"); + break; + case MT32Emu::ReportType_available3DNow: + debug(1, "MT32emu: 3DNow! is available"); + break; + case MT32Emu::ReportType_using3DNow: + debug(1, "MT32emu: using 3DNow!"); + break; + default: + break; + } + return 0; +} + +//////////////////////////////////////// +// +// MidiDriver_MT32 +// +//////////////////////////////////////// + +MidiDriver_MT32::MidiDriver_MT32(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer) { + _channelMask = 0xFFFF; // Permit all 16 channels by default + uint i; + for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) { + _midiChannels[i].init(this, i); + } + _synth = NULL; + // A higher baseFreq reduces the length used in generateSamples(), + // and means that the timer callback will be called more often. + // That results in more accurate timing. + _baseFreq = 10000; + // Unfortunately bugs in the emulator cause inaccurate tuning + // at rates other than 32KHz, thus we produce data at 32KHz and + // rely on Mixer to convert. + _outputRate = 32000; //_mixer->getOutputRate(); + _initialising = false; +} + +MidiDriver_MT32::~MidiDriver_MT32() { + delete _synth; +} + +int MidiDriver_MT32::open() { + MT32Emu::SynthProperties prop; + + if (_isOpen) + return MERR_ALREADY_OPEN; + + MidiDriver_Emulated::open(); + + memset(&prop, 0, sizeof(prop)); + prop.sampleRate = getRate(); + prop.useReverb = true; + prop.useDefaultReverb = false; + prop.reverbType = 0; + prop.reverbTime = 5; + prop.reverbLevel = 3; + prop.userData = this; + prop.printDebug = MT32_PrintDebug; + prop.report = MT32_Report; + prop.openFile = MT32_OpenFile; + + _synth = new MT32Emu::Synth(); + + Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); + + if (screenFormat.bytesPerPixel == 1) { + const byte dummy_palette[] = { + 0, 0, 0, 0, // background + 0, 171, 0, 0, // border, font + 171, 0, 0, 0 // fill + }; + + g_system->getPaletteManager()->setPalette(dummy_palette, 0, 3); + } + + _initialising = true; + drawMessage(-1, _s("Initialising MT-32 Emulator")); + if (!_synth->open(prop)) + return MERR_DEVICE_NOT_AVAILABLE; + _initialising = false; + + if (screenFormat.bytesPerPixel > 1) + g_system->fillScreen(screenFormat.RGBToColor(0, 0, 0)); + else + g_system->fillScreen(0); + + g_system->updateScreen(); + + _mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + + return 0; +} + +void MidiDriver_MT32::send(uint32 b) { + _synth->playMsg(b); +} + +void MidiDriver_MT32::setPitchBendRange(byte channel, uint range) { + if (range > 24) { + printf("setPitchBendRange() called with range > 24: %d", range); + } + byte benderRangeSysex[9]; + benderRangeSysex[0] = 0x41; // Roland + benderRangeSysex[1] = channel; + benderRangeSysex[2] = 0x16; // MT-32 + benderRangeSysex[3] = 0x12; // Write + benderRangeSysex[4] = 0x00; + benderRangeSysex[5] = 0x00; + benderRangeSysex[6] = 0x04; + benderRangeSysex[7] = (byte)range; + benderRangeSysex[8] = MT32Emu::Synth::calcSysexChecksum(&benderRangeSysex[4], 4, 0); + sysEx(benderRangeSysex, 9); +} + +void MidiDriver_MT32::sysEx(const byte *msg, uint16 length) { + if (msg[0] == 0xf0) { + _synth->playSysex(msg, length); + } else { + _synth->playSysexWithoutFraming(msg, length); + } +} + +void MidiDriver_MT32::close() { + if (!_isOpen) + return; + _isOpen = false; + + // Detach the player callback handler + setTimerCallback(NULL, NULL); + // Detach the mixer callback handler + _mixer->stopHandle(_handle); + + _synth->close(); + delete _synth; + _synth = NULL; +} + +void MidiDriver_MT32::generateSamples(int16 *data, int len) { + _synth->render(data, len); +} + +uint32 MidiDriver_MT32::property(int prop, uint32 param) { + switch (prop) { + case PROP_CHANNEL_MASK: + _channelMask = param & 0xFFFF; + return 1; + } + + return 0; +} + +MidiChannel *MidiDriver_MT32::allocateChannel() { + MidiChannel_MT32 *chan; + uint i; + + for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) { + if (i == 9 || !(_channelMask & (1 << i))) + continue; + chan = &_midiChannels[i]; + if (chan->allocate()) { + return chan; + } + } + return NULL; +} + +MidiChannel *MidiDriver_MT32::getPercussionChannel() { + return &_midiChannels[9]; +} + +// This code should be used when calling the timer callback from the mixer thread is undesirable. +// Note that it results in less accurate timing. +#if 0 +class MidiEvent_MT32 { +public: + MidiEvent_MT32 *_next; + uint32 _msg; // 0xFFFFFFFF indicates a sysex message + byte *_data; + uint32 _len; + + MidiEvent_MT32(uint32 msg, byte *data, uint32 len) { + _msg = msg; + if (len > 0) { + _data = new byte[len]; + memcpy(_data, data, len); + } + _len = len; + _next = NULL; + } + + MidiEvent_MT32() { + if (_len > 0) + delete _data; + } +}; + +class MidiDriver_ThreadedMT32 : public MidiDriver_MT32 { +private: + OSystem::Mutex _eventMutex; + MidiEvent_MT32 *_events; + TimerManager::TimerProc _timer_proc; + + void pushMidiEvent(MidiEvent_MT32 *event); + MidiEvent_MT32 *popMidiEvent(); + +protected: + void send(uint32 b); + void sysEx(const byte *msg, uint16 length); + +public: + MidiDriver_ThreadedMT32(Audio::Mixer *mixer); + + void onTimer(); + void close(); + void setTimerCallback(void *timer_param, TimerManager::TimerProc timer_proc); +}; + + +MidiDriver_ThreadedMT32::MidiDriver_ThreadedMT32(Audio::Mixer *mixer) : MidiDriver_MT32(mixer) { + _events = NULL; + _timer_proc = NULL; +} + +void MidiDriver_ThreadedMT32::close() { + MidiDriver_MT32::close(); + while ((popMidiEvent() != NULL)) { + // Just eat any leftover events + } +} + +void MidiDriver_ThreadedMT32::setTimerCallback(void *timer_param, TimerManager::TimerProc timer_proc) { + if (!_timer_proc || !timer_proc) { + if (_timer_proc) + _vm->_timer->removeTimerProc(_timer_proc); + _timer_proc = timer_proc; + if (timer_proc) + _vm->_timer->installTimerProc(timer_proc, getBaseTempo(), timer_param); + } +} + +void MidiDriver_ThreadedMT32::pushMidiEvent(MidiEvent_MT32 *event) { + Common::StackLock lock(_eventMutex); + if (_events == NULL) { + _events = event; + } else { + MidiEvent_MT32 *last = _events; + while (last->_next != NULL) + last = last->_next; + last->_next = event; + } +} + +MidiEvent_MT32 *MidiDriver_ThreadedMT32::popMidiEvent() { + Common::StackLock lock(_eventMutex); + MidiEvent_MT32 *event; + event = _events; + if (event != NULL) + _events = event->_next; + return event; +} + +void MidiDriver_ThreadedMT32::send(uint32 b) { + MidiEvent_MT32 *event = new MidiEvent_MT32(b, NULL, 0); + pushMidiEvent(event); +} + +void MidiDriver_ThreadedMT32::sysEx(const byte *msg, uint16 length) { + MidiEvent_MT32 *event = new MidiEvent_MT32(0xFFFFFFFF, msg, length); + pushMidiEvent(event); +} + +void MidiDriver_ThreadedMT32::onTimer() { + MidiEvent_MT32 *event; + while ((event = popMidiEvent()) != NULL) { + if (event->_msg == 0xFFFFFFFF) { + MidiDriver_MT32::sysEx(event->_data, event->_len); + } else { + MidiDriver_MT32::send(event->_msg); + } + delete event; + } +} +#endif + + +// Plugin interface + +class MT32EmuMusicPlugin : public MusicPluginObject { +public: + const char *getName() const { + return _s("MT-32 Emulator"); + } + + const char *getId() const { + return "mt32"; + } + + MusicDevices getDevices() const; + Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const; +}; + +MusicDevices MT32EmuMusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_MT32)); + return devices; +} + +Common::Error MT32EmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const { + if (ConfMan.hasKey("extrapath")) + SearchMan.addDirectory("extrapath", ConfMan.get("extrapath")); + + *mididriver = new MidiDriver_MT32(g_system->getMixer()); + + return Common::kNoError; +} + +//#if PLUGIN_ENABLED_DYNAMIC(MT32) + //REGISTER_PLUGIN_DYNAMIC(MT32, PLUGIN_TYPE_MUSIC, MT32EmuMusicPlugin); +//#else + REGISTER_PLUGIN_STATIC(MT32, PLUGIN_TYPE_MUSIC, MT32EmuMusicPlugin); +//#endif + +#endif diff --git a/audio/softsynth/mt32/freeverb.cpp b/audio/softsynth/mt32/freeverb.cpp new file mode 100644 index 0000000000..c62d4f2cf3 --- /dev/null +++ b/audio/softsynth/mt32/freeverb.cpp @@ -0,0 +1,310 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Comb filter implementation +// +// Written by +// http://www.dreampoint.co.uk +// This code is public domain + +#include "audio/softsynth/mt32/freeverb.h" + +comb::comb() { + filterstore = 0; + bufidx = 0; +} + +void comb::setbuffer(float *buf, int size) { + buffer = buf; + bufsize = size; +} + +void comb::mute() { + for (int i = 0; i < bufsize; i++) + buffer[i] = 0; +} + +void comb::setdamp(float val) { + damp1 = val; + damp2 = 1 - val; +} + +float comb::getdamp() { + return damp1; +} + +void comb::setfeedback(float val) { + feedback = val; +} + +float comb::getfeedback() { + return feedback; +} + +// Allpass filter implementation + +allpass::allpass() { + bufidx = 0; +} + +void allpass::setbuffer(float *buf, int size) { + buffer = buf; + bufsize = size; +} + +void allpass::mute() { + for (int i = 0; i < bufsize; i++) + buffer[i] = 0; +} + +void allpass::setfeedback(float val) { + feedback = val; +} + +float allpass::getfeedback() { + return feedback; +} + +// Reverb model implementation + +revmodel::revmodel() { + // Tie the components to their buffers + combL[0].setbuffer(bufcombL1,combtuningL1); + combR[0].setbuffer(bufcombR1,combtuningR1); + combL[1].setbuffer(bufcombL2,combtuningL2); + combR[1].setbuffer(bufcombR2,combtuningR2); + combL[2].setbuffer(bufcombL3,combtuningL3); + combR[2].setbuffer(bufcombR3,combtuningR3); + combL[3].setbuffer(bufcombL4,combtuningL4); + combR[3].setbuffer(bufcombR4,combtuningR4); + combL[4].setbuffer(bufcombL5,combtuningL5); + combR[4].setbuffer(bufcombR5,combtuningR5); + combL[5].setbuffer(bufcombL6,combtuningL6); + combR[5].setbuffer(bufcombR6,combtuningR6); + combL[6].setbuffer(bufcombL7,combtuningL7); + combR[6].setbuffer(bufcombR7,combtuningR7); + combL[7].setbuffer(bufcombL8,combtuningL8); + combR[7].setbuffer(bufcombR8,combtuningR8); + allpassL[0].setbuffer(bufallpassL1,allpasstuningL1); + allpassR[0].setbuffer(bufallpassR1,allpasstuningR1); + allpassL[1].setbuffer(bufallpassL2,allpasstuningL2); + allpassR[1].setbuffer(bufallpassR2,allpasstuningR2); + allpassL[2].setbuffer(bufallpassL3,allpasstuningL3); + allpassR[2].setbuffer(bufallpassR3,allpasstuningR3); + allpassL[3].setbuffer(bufallpassL4,allpasstuningL4); + allpassR[3].setbuffer(bufallpassR4,allpasstuningR4); + + // Set default values + allpassL[0].setfeedback(0.5f); + allpassR[0].setfeedback(0.5f); + allpassL[1].setfeedback(0.5f); + allpassR[1].setfeedback(0.5f); + allpassL[2].setfeedback(0.5f); + allpassR[2].setfeedback(0.5f); + allpassL[3].setfeedback(0.5f); + allpassR[3].setfeedback(0.5f); + setmode(initialmode); + setwet(initialwet); + setroomsize(initialroom); + setdry(initialdry); + setdamp(initialdamp); + setwidth(initialwidth); + + // Buffer will be full of rubbish - so we MUST mute them + mute(); +} + +void revmodel::mute() { + int i; + + if (getmode() >= freezemode) + return; + + for (i = 0; i < numcombs; i++) { + combL[i].mute(); + combR[i].mute(); + } + + for (i = 0; i < numallpasses; i++) { + allpassL[i].mute(); + allpassR[i].mute(); + } +} + +void revmodel::processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) { + float outL, outR, input; + + while (numsamples-- > 0) { + int i; + + outL = outR = 0; + input = (*inputL + *inputR) * gain; + + // Accumulate comb filters in parallel + for (i = 0; i < numcombs; i++) { + outL += combL[i].process(input); + outR += combR[i].process(input); + } + + // Feed through allpasses in series + for (i = 0; i < numallpasses; i++) { + outL = allpassL[i].process(outL); + outR = allpassR[i].process(outR); + } + + // Calculate output REPLACING anything already there + *outputL = outL * wet1 + outR * wet2 + *inputL * dry; + *outputR = outR * wet1 + outL * wet2 + *inputR * dry; + + // Increment sample pointers, allowing for interleave (if any) + inputL += skip; + inputR += skip; + outputL += skip; + outputR += skip; + } +} + +void revmodel::processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) { + float outL, outR, input; + + while (numsamples-- > 0) { + int i; + + outL = outR = 0; + input = (*inputL + *inputR) * gain; + + // Accumulate comb filters in parallel + for (i = 0; i < numcombs; i++) { + outL += combL[i].process(input); + outR += combR[i].process(input); + } + + // Feed through allpasses in series + for (i = 0; i < numallpasses; i++) { + outL = allpassL[i].process(outL); + outR = allpassR[i].process(outR); + } + + // Calculate output MIXING with anything already there + *outputL += outL * wet1 + outR * wet2 + *inputL * dry; + *outputR += outR * wet1 + outL * wet2 + *inputR * dry; + + // Increment sample pointers, allowing for interleave (if any) + inputL += skip; + inputR += skip; + outputL += skip; + outputR += skip; + } +} + +void revmodel::update() { + // Recalculate internal values after parameter change + + int i; + + wet1 = wet * (width / 2 + 0.5f); + wet2 = wet * ((1 - width) / 2); + + if (mode >= freezemode) { + roomsize1 = 1; + damp1 = 0; + gain = muted; + } else { + roomsize1 = roomsize; + damp1 = damp; + gain = fixedgain; + } + + for (i = 0; i < numcombs; i++) { + combL[i].setfeedback(roomsize1); + combR[i].setfeedback(roomsize1); + } + + for (i = 0; i < numcombs; i++) { + combL[i].setdamp(damp1); + combR[i].setdamp(damp1); + } +} + +// The following get/set functions are not inlined, because +// speed is never an issue when calling them, and also +// because as you develop the reverb model, you may +// wish to take dynamic action when they are called. + +void revmodel::setroomsize(float value) { + roomsize = (value * scaleroom) + offsetroom; + update(); +} + +float revmodel::getroomsize() { + return (roomsize - offsetroom) / scaleroom; +} + +void revmodel::setdamp(float value) { + damp = value * scaledamp; + update(); +} + +float revmodel::getdamp() { + return damp / scaledamp; +} + +void revmodel::setwet(float value) { + wet = value * scalewet; + update(); +} + +float revmodel::getwet() { + return wet / scalewet; +} + +void revmodel::setdry(float value) { + dry = value * scaledry; +} + +float revmodel::getdry() { + return dry / scaledry; +} + +void revmodel::setwidth(float value) { + width = value; + update(); +} + +float revmodel::getwidth() { + return width; +} + +void revmodel::setmode(float value) { + mode = value; + update(); +} + +float revmodel::getmode() { + if (mode >= freezemode) + return 1; + else + return 0; +} diff --git a/audio/softsynth/mt32/freeverb.h b/audio/softsynth/mt32/freeverb.h new file mode 100644 index 0000000000..8310aca3e3 --- /dev/null +++ b/audio/softsynth/mt32/freeverb.h @@ -0,0 +1,244 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Macro for killing denormalled numbers +// +// Written by Jezar at Dreampoint, June 2000 +// http://www.dreampoint.co.uk +// Based on IS_DENORMAL macro by Jon Watte +// This code is public domain + +#ifndef FREEVERB_H +#define FREEVERB_H + +// FIXME: Fix this really ugly hack +inline float undenormalise(void *sample) { + if (((*(unsigned int*)sample) & 0x7f800000) == 0) + return 0.0f; + return *(float*)sample; +} + +// Comb filter class declaration + +class comb { +public: + comb(); + void setbuffer(float *buf, int size); + inline float process(float inp); + void mute(); + void setdamp(float val); + float getdamp(); + void setfeedback(float val); + float getfeedback(); +private: + float feedback; + float filterstore; + float damp1; + float damp2; + float *buffer; + int bufsize; + int bufidx; +}; + + +// Big to inline - but crucial for speed + +inline float comb::process(float input) { + float output; + + output = buffer[bufidx]; + undenormalise(&output); + + filterstore = (output * damp2) + (filterstore * damp1); + undenormalise(&filterstore); + + buffer[bufidx] = input + (filterstore * feedback); + + if (++bufidx >= bufsize) + bufidx = 0; + + return output; +} + +// Allpass filter declaration + +class allpass { +public: + allpass(); + void setbuffer(float *buf, int size); + inline float process(float inp); + void mute(); + void setfeedback(float val); + float getfeedback(); +private: + float feedback; + float *buffer; + int bufsize; + int bufidx; +}; + + +// Big to inline - but crucial for speed + +inline float allpass::process(float input) { + float output; + float bufout; + + bufout = buffer[bufidx]; + undenormalise(&bufout); + + output = -input + bufout; + buffer[bufidx] = input + (bufout * feedback); + + if (++bufidx >= bufsize) + bufidx = 0; + + return output; +} + + +// Reverb model tuning values + +const int numcombs = 8; +const int numallpasses = 4; +const float muted = 0; +const float fixedgain = 0.015f; +const float scalewet = 3; +const float scaledry = 2; +const float scaledamp = 0.4f; +const float scaleroom = 0.28f; +const float offsetroom = 0.7f; +const float initialroom = 0.5f; +const float initialdamp = 0.5f; +const float initialwet = 1 / scalewet; +const float initialdry = 0; +const float initialwidth = 1; +const float initialmode = 0; +const float freezemode = 0.5f; +const int stereospread = 23; + +// These values assume 44.1KHz sample rate +// they will probably be OK for 48KHz sample rate +// but would need scaling for 96KHz (or other) sample rates. +// The values were obtained by listening tests. +const int combtuningL1 = 1116; +const int combtuningR1 = 1116 + stereospread; +const int combtuningL2 = 1188; +const int combtuningR2 = 1188 + stereospread; +const int combtuningL3 = 1277; +const int combtuningR3 = 1277 + stereospread; +const int combtuningL4 = 1356; +const int combtuningR4 = 1356 + stereospread; +const int combtuningL5 = 1422; +const int combtuningR5 = 1422 + stereospread; +const int combtuningL6 = 1491; +const int combtuningR6 = 1491 + stereospread; +const int combtuningL7 = 1557; +const int combtuningR7 = 1557 + stereospread; +const int combtuningL8 = 1617; +const int combtuningR8 = 1617 + stereospread; +const int allpasstuningL1 = 556; +const int allpasstuningR1 = 556 + stereospread; +const int allpasstuningL2 = 441; +const int allpasstuningR2 = 441 + stereospread; +const int allpasstuningL3 = 341; +const int allpasstuningR3 = 341 + stereospread; +const int allpasstuningL4 = 225; +const int allpasstuningR4 = 225 + stereospread; + + +// Reverb model declaration + +class revmodel { +public: + revmodel(); + void mute(); + void processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); + void processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); + void setroomsize(float value); + float getroomsize(); + void setdamp(float value); + float getdamp(); + void setwet(float value); + float getwet(); + void setdry(float value); + float getdry(); + void setwidth(float value); + float getwidth(); + void setmode(float value); + float getmode(); +private: + void update(); + + float gain; + float roomsize, roomsize1; + float damp, damp1; + float wet, wet1, wet2; + float dry; + float width; + float mode; + + // The following are all declared inline + // to remove the need for dynamic allocation + // with its subsequent error-checking messiness + + // Comb filters + comb combL[numcombs]; + comb combR[numcombs]; + + // Allpass filters + allpass allpassL[numallpasses]; + allpass allpassR[numallpasses]; + + // Buffers for the combs + float bufcombL1[combtuningL1]; + float bufcombR1[combtuningR1]; + float bufcombL2[combtuningL2]; + float bufcombR2[combtuningR2]; + float bufcombL3[combtuningL3]; + float bufcombR3[combtuningR3]; + float bufcombL4[combtuningL4]; + float bufcombR4[combtuningR4]; + float bufcombL5[combtuningL5]; + float bufcombR5[combtuningR5]; + float bufcombL6[combtuningL6]; + float bufcombR6[combtuningR6]; + float bufcombL7[combtuningL7]; + float bufcombR7[combtuningR7]; + float bufcombL8[combtuningL8]; + float bufcombR8[combtuningR8]; + + // Buffers for the allpasses + float bufallpassL1[allpasstuningL1]; + float bufallpassR1[allpasstuningR1]; + float bufallpassL2[allpasstuningL2]; + float bufallpassR2[allpasstuningR2]; + float bufallpassL3[allpasstuningL3]; + float bufallpassR3[allpasstuningR3]; + float bufallpassL4[allpasstuningL4]; + float bufallpassR4[allpasstuningR4]; +}; + +#endif diff --git a/audio/softsynth/mt32/i386.cpp b/audio/softsynth/mt32/i386.cpp new file mode 100644 index 0000000000..f092189d76 --- /dev/null +++ b/audio/softsynth/mt32/i386.cpp @@ -0,0 +1,849 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "mt32emu.h" + +#ifdef MT32EMU_HAVE_X86 + +namespace MT32Emu { + +#ifndef _MSC_VER + +#define eflag(value) __asm__ __volatile__("pushfl \n popfl \n" : : "a"(value)) +#define cpuid_flag (1 << 21) + +static inline bool atti386_DetectCPUID() { + unsigned int result; + + // Is there a cpuid? + result = cpuid_flag; // set test + eflag(result); + if (!(result & cpuid_flag)) + return false; + + result = 0; // clear test + eflag(result); + if (result & cpuid_flag) + return false; + + return true; +} + +static inline bool atti386_DetectSIMD() { + unsigned int result; + + if (atti386_DetectCPUID() == false) + return false; + + /* check cpuid */ + __asm__ __volatile__( + "pushl %%ebx \n" \ + "movl $1, %%eax \n" \ + "cpuid \n" \ + "movl %%edx, %0 \n" \ + "popl %%ebx \n" \ + : "=r"(result) : : "eax", "ecx", "edx"); + + if (result & (1 << 25)) + return true; + + return false; +} + +static inline bool atti386_Detect3DNow() { + unsigned int result; + + if (atti386_DetectCPUID() == false) + return false; + + // get cpuid + __asm__ __volatile__( + "pushl %%ebx \n" \ + "movl $0x80000001, %%eax \n" \ + "cpuid \n" \ + "movl %%edx, %0 \n" \ + "popl %%ebx \n" \ + : "=r"(result) : : "eax", "ecx", "edx"); + + if (result & 0x80000000) + return true; + + return false; +} + + +static inline float atti386_iir_filter_sse(float *output, float *hist1_ptr, float *coef_ptr) { + __asm__ __volatile__ ( + "pushl %1 \n" \ + "pushl %2 \n" \ + "movss 0(%0), %%xmm1 \n" \ + "movups 0(%1), %%xmm2 \n" \ + "movlps 0(%2), %%xmm3 \n" \ + " \n" \ + "shufps $0x44, %%xmm3, %%xmm3 \n" \ + " \n" \ + "mulps %%xmm3, %%xmm2 \n" \ + " \n" \ + "subss %%xmm2, %%xmm1 \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "subss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm1, 0(%2) \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm3, 4(%2) \n" \ + " \n" \ + "addl $16, %1 \n" \ + "addl $8, %2 \n" \ + " \n" \ + "movups 0(%1), %%xmm2 \n" \ + " \n" \ + "movlps 0(%2), %%xmm3 \n" \ + "shufps $0x44, %%xmm3, %%xmm3 \n" \ + " \n" \ + "mulps %%xmm3, %%xmm2 \n" \ + " \n" \ + "subss %%xmm2, %%xmm1 \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "subss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm1, 0(%2) \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm3, 4(%2) \n" \ + "movss %%xmm1, 0(%0) \n" \ + "popl %2 \n" \ + "popl %1 \n" \ + : : "r"(output), "r"(coef_ptr), "r"(hist1_ptr) + : "memory" +#ifdef __SSE__ + , "xmm1", "xmm2", "xmm3" +#endif + ); + + return *output; +} + +static inline float atti386_iir_filter_3DNow(float output, float *hist1_ptr, float *coef_ptr) { + float tmp; + + __asm__ __volatile__ ( + "movq %0, %%mm1 \n" \ + " \n" \ + "movl %1, %%edi \n" \ + "movq 0(%%edi), %%mm2 \n" \ + " \n" \ + "movl %2, %%eax; \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "movd %%mm1, %3 \n" \ + " \n" \ + "addl $8, %%edi \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "pushl %3 \n" \ + "popl 0(%%eax) \n" \ + " \n" \ + "movd %%mm3, 4(%%eax) \n" \ + " \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%eax \n" \ + " \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "movd %%mm1, %3 \n" \ + " \n" \ + "addl $8, %%edi \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "pushl %3 \n" \ + "popl 0(%%eax) \n" \ + "movd %%mm3, 4(%%eax) \n" \ + " \n" \ + "movd %%mm1, %0 \n" \ + "femms \n" \ + : "=m"(output) : "g"(coef_ptr), "g"(hist1_ptr), "m"(tmp) + : "eax", "edi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); + + return output; +} + +static inline void atti386_produceOutput1(int tmplen, Bit16s myvolume, Bit16s *useBuf, Bit16s *snd) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movw %1, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %1, %%ax \n" \ + "movd %%eax, %%mm3 \n" \ + "movd %%eax, %%mm2 \n" \ + "psllq $32, %%mm3 \n" \ + "por %%mm2, %%mm3 \n" \ + "movl %2, %%esi \n" \ + "movl %3, %%edi \n" \ + "1: \n" \ + "movq 0(%%esi), %%mm1 \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "pmulhw %%mm3, %%mm1 \n" \ + "paddw %%mm2, %%mm1 \n" \ + "movq %%mm1, 0(%%edi) \n" \ + " \n" \ + "addl $8, %%esi \n" \ + "addl $8, %%edi \n" \ + " \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(tmplen), "g"(myvolume), "g"(useBuf), "g"(snd) + : "eax", "ecx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); +} + +static inline void atti386_produceOutput2(Bit32u len, Bit16s *snd, float *sndbufl, float *sndbufr, float *multFactor) { + __asm__ __volatile__( + "movl %4, %%ecx \n" \ + "shrl $1, %%ecx \n" \ + "addl $4, %%ecx \n" \ + "pushl %%ecx \n" \ + " \n" \ + "movl %0, %%esi \n" \ + "movups 0(%%esi), %%xmm1 \n" \ + " \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "xorl %%eax, %%eax \n" \ + "movw 0(%1), %%ax \n" \ + "cwde \n" \ + "incl %1 \n" \ + "incl %1 \n" \ + "movd %%eax, %%mm1 \n" \ + "psrlq $32, %%mm1 \n" \ + "movw 0(%1), %%ax \n" \ + "incl %1 \n" \ + "incl %1 \n" \ + "movd %%eax, %%mm2 \n" \ + "por %%mm2, %%mm1 \n" \ + " \n" \ + "decl %%ecx \n" \ + "jnz 1b \n" \ + " \n" \ + "popl %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %3, %%edi \n" \ + "incl %%esi \n" \ + "2: \n" \ + "decl %%ecx \n" \ + "jnz 2b \n" \ + : : "g"(multFactor), "r"(snd), "g"(sndbufl), "g"(sndbufr), "g"(len) + : "eax", "ecx", "edi", "esi", "mm1", "mm2", "xmm1", "memory"); +} + +static inline void atti386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "movq 0(%%edi), %%mm1 \n" \ + "movq 0(%%esi), %%mm2 \n" \ + "paddw %%mm2, %%mm1 \n" \ + "movq %%mm1, 0(%%esi) \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%esi \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(len), "g"(buf1), "g"(buf2) + : "ecx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2" +#endif + ); +} + +static inline void atti386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "movq 0(%%esi), %%mm1 \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "movq %%mm1, %%mm3 \n" \ + "pmulhw %%mm2, %%mm1 \n" \ + "paddw %%mm3, %%mm1 \n" \ + "movq %%mm1, 0(%%esi) \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%esi \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(len), "g"(buf1), "g"(buf2) + : "ecx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); +} + +static inline void atti386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "movq 0(%%esi), %%mm1 \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "pmulhw %%mm2, %%mm1 \n" \ + "movq %%mm1, 0(%%esi) \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%esi \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(len), "g"(buf1), "g"(buf2) + : "ecx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2" +#endif + ); +} + +static inline void atti386_partialProductOutput(int quadlen, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *p1buf) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movw %1, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %2, %%ax \n" \ + "movd %%eax, %%mm1 \n" \ + "movd %%eax, %%mm2 \n" \ + "psllq $32, %%mm1 \n" \ + "por %%mm2, %%mm1 \n" \ + "movl %3, %%edi \n" \ + "movl %4, %%esi \n" \ + "pushl %%ebx \n" \ + "1: \n" \ + "movw 0(%%esi), %%bx \n" \ + "addl $2, %%esi \n" \ + "movw 0(%%esi), %%dx \n" \ + "addl $2, %%esi \n" \ + "" \ + "movw %%dx, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %%dx, %%ax \n" \ + "movd %%eax, %%mm2 \n" \ + "psllq $32, %%mm2 \n" \ + "movw %%bx, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %%bx, %%ax \n" \ + "movd %%eax, %%mm3 \n" \ + "por %%mm3, %%mm2 \n" \ + "" \ + "pmulhw %%mm1, %%mm2 \n" \ + "movq %%mm2, 0(%%edi) \n" \ + "addl $8, %%edi \n" \ + "" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + "popl %%ebx \n" \ + : : "g"(quadlen), "g"(leftvol), "g"(rightvol), "g"(partialBuf), "g"(p1buf) + : "eax", "ecx", "edx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); +} + +#endif + +bool DetectSIMD() { +#ifdef _MSC_VER + bool found_simd; + __asm { + pushfd + pop eax // get EFLAGS into eax + mov ebx,eax // keep a copy + xor eax,0x200000 + // toggle CPUID bit + + push eax + popfd // set new EFLAGS + pushfd + pop eax // EFLAGS back into eax + + xor eax,ebx + // have we changed the ID bit? + + je NO_SIMD + // No, no CPUID instruction + + // we could toggle the + // ID bit so CPUID is present + mov eax,1 + + cpuid // get processor features + test edx,1<<25 // check the SIMD bit + jz NO_SIMD + mov found_simd,1 + jmp DONE + NO_SIMD: + mov found_simd,0 + DONE: + } + return found_simd; +#else + return atti386_DetectSIMD(); +#endif +} + +bool Detect3DNow() { +#ifdef _MSC_VER + bool found3D = false; + __asm { + pushfd + pop eax + mov edx, eax + xor eax, 00200000h + push eax + popfd + pushfd + pop eax + xor eax, edx + jz NO_3DNOW + + mov eax, 80000000h + cpuid + + cmp eax, 80000000h + jbe NO_3DNOW + + mov eax, 80000001h + cpuid + test edx, 80000000h + jz NO_3DNOW + mov found3D, 1 +NO_3DNOW: + + } + return found3D; +#else + return atti386_Detect3DNow(); +#endif +} + +float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr) { + float output; + + // 1st number of coefficients array is overall input scale factor, or filter gain + output = input * (*coef_ptr++); + +#ifdef _MSC_VER + __asm { + + movss xmm1, output + + mov eax, coef_ptr + movups xmm2, [eax] + + mov eax, hist1_ptr + movlps xmm3, [eax] + shufps xmm3, xmm3, 44h + // hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr + + mulps xmm2, xmm3 + + subss xmm1, xmm2 + // Rotate elements right + shufps xmm2, xmm2, 39h + subss xmm1, xmm2 + + // Store new_hist + movss DWORD PTR [eax], xmm1 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Store previous hist + movss DWORD PTR [eax+4], xmm3 + + add coef_ptr, 16 + add hist1_ptr, 8 + + mov eax, coef_ptr + movups xmm2, [eax] + + mov eax, hist1_ptr + movlps xmm3, [eax] + shufps xmm3, xmm3, 44h + // hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr + + mulps xmm2, xmm3 + + subss xmm1, xmm2 + // Rotate elements right + shufps xmm2, xmm2, 39h + subss xmm1, xmm2 + + // Store new_hist + movss DWORD PTR [eax], xmm1 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Store previous hist + movss DWORD PTR [eax+4], xmm3 + + movss output, xmm1 + } +#else + output = atti386_iir_filter_sse(&output, hist1_ptr, coef_ptr); +#endif + return output; +} + +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr) { + float output; + + // 1st number of coefficients array is overall input scale factor, or filter gain + output = input * (*coef_ptr++); + + // I find it very sad that 3DNow requires twice as many instructions as Intel's SSE + // Intel does have the upper hand here. +#ifdef _MSC_VER + float tmp; + __asm { + movq mm1, output + mov ebx, coef_ptr + movq mm2, [ebx] + + mov eax, hist1_ptr; + movq mm3, [eax] + + pfmul mm2, mm3 + pfsub mm1, mm2 + + psrlq mm2, 32 + pfsub mm1, mm2 + + // Store new hist + movd tmp, mm1 + + add ebx, 8 + movq mm2, [ebx] + movq mm3, [eax] + + pfmul mm2, mm3 + pfadd mm1, mm2 + + psrlq mm2, 32 + pfadd mm1, mm2 + + push tmp + pop DWORD PTR [eax] + + movd DWORD PTR [eax+4], mm3 + + add ebx, 8 + add eax, 8 + + movq mm2, [ebx] + movq mm3, [eax] + + pfmul mm2, mm3 + pfsub mm1, mm2 + + psrlq mm2, 32 + pfsub mm1, mm2 + + // Store new hist + movd tmp, mm1 + + add ebx, 8 + movq mm2, [ebx] + movq mm3, [eax] + + pfmul mm2, mm3 + pfadd mm1, mm2 + + psrlq mm2, 32 + pfadd mm1, mm2 + + push tmp + pop DWORD PTR [eax] + movd DWORD PTR [eax+4], mm3 + + movd output, mm1 + + femms + } +#else + output = atti386_iir_filter_3DNow(output, hist1_ptr, coef_ptr); +#endif + return output; +} + +#if MT32EMU_USE_MMX > 0 + +int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf) { + int tmplen = len >> 1; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx,tmplen + mov ax, leftvol + shl eax,16 + mov ax, rightvol + movd mm1, eax + movd mm2, eax + psllq mm1, 32 + por mm1, mm2 + mov edi, partialBuf + mov esi, mixedBuf +mmxloop1: + mov bx, [esi] + add esi,2 + mov dx, [esi] + add esi,2 + + mov ax, dx + shl eax, 16 + mov ax, dx + movd mm2,eax + psllq mm2, 32 + mov ax, bx + shl eax, 16 + mov ax, bx + movd mm3,eax + por mm2,mm3 + + pmulhw mm2, mm1 + movq [edi], mm2 + add edi, 8 + + dec ecx + cmp ecx,0 + jg mmxloop1 + emms + } +#else + atti386_partialProductOutput(tmplen, leftvol, rightvol, partialBuf, mixedBuf); +#endif + return tmplen << 1; +} + +int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { + int tmplen = len >> 2; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov esi, buf1 + mov edi, buf2 + +mixloop1: + movq mm1, [edi] + movq mm2, [esi] + paddw mm1,mm2 + movq [esi],mm1 + add edi,8 + add esi,8 + + dec ecx + cmp ecx,0 + jg mixloop1 + emms + } +#else + atti386_mixBuffers(buf1, buf2, tmplen); +#endif + return tmplen << 2; +} + + +int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { + int tmplen = len >> 2; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov esi, buf1 + mov edi, buf2 + +mixloop2: + movq mm1, [esi] + movq mm2, [edi] + movq mm3, mm1 + pmulhw mm1, mm2 + paddw mm1,mm3 + movq [esi],mm1 + add edi,8 + add esi,8 + + dec ecx + cmp ecx,0 + jg mixloop2 + emms + } +#else + atti386_mixBuffersRingMix(buf1, buf2, tmplen); +#endif + return tmplen << 2; +} + +int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { + int tmplen = len >> 2; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov esi, buf1 + mov edi, buf2 + +mixloop3: + movq mm1, [esi] + movq mm2, [edi] + pmulhw mm1, mm2 + movq [esi],mm1 + add edi,8 + add esi,8 + + dec ecx + cmp ecx,0 + jg mixloop3 + emms + } +#else + atti386_mixBuffersRing(buf1, buf2, tmplen); +#endif + return tmplen << 2; +} + +int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) { + int tmplen = (len >> 1); + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov ax,volume + shl eax,16 + mov ax,volume + movd mm3,eax + movd mm2,eax + psllq mm3, 32 + por mm3,mm2 + mov esi, useBuf + mov edi, stream +mixloop4: + movq mm1, [esi] + movq mm2, [edi] + pmulhw mm1, mm3 + paddw mm1,mm2 + movq [edi], mm1 + + add esi,8 + add edi,8 + + dec ecx + cmp ecx,0 + jg mixloop4 + emms + } +#else + atti386_produceOutput1(tmplen, volume, useBuf, stream); +#endif + return tmplen << 1; +} + +#endif + +} + +#endif diff --git a/audio/softsynth/mt32/i386.h b/audio/softsynth/mt32/i386.h new file mode 100644 index 0000000000..e8644411cd --- /dev/null +++ b/audio/softsynth/mt32/i386.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_I386_H +#define MT32EMU_I386_H + +namespace MT32Emu { +#ifdef MT32EMU_HAVE_X86 + +// Function that detects the availablity of SSE SIMD instructions +bool DetectSIMD(); +// Function that detects the availablity of 3DNow instructions +bool Detect3DNow(); + +float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr); +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr); +float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr); + +#if MT32EMU_USE_MMX > 0 +int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf); +int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len); +int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len); +int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len); +int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume); +#endif + +#endif + +} + +#endif diff --git a/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk new file mode 100644 index 0000000000..a8329bc98c --- /dev/null +++ b/audio/softsynth/mt32/module.mk @@ -0,0 +1,14 @@ +MODULE := audio/softsynth/mt32 + +MODULE_OBJS := \ + mt32_file.o \ + i386.o \ + part.o \ + partial.o \ + partialManager.o \ + synth.o \ + tables.o \ + freeverb.o + +# Include common rules +include $(srcdir)/rules.mk diff --git a/audio/softsynth/mt32/mt32_file.cpp b/audio/softsynth/mt32/mt32_file.cpp new file mode 100644 index 0000000000..cdf9fa13f6 --- /dev/null +++ b/audio/softsynth/mt32/mt32_file.cpp @@ -0,0 +1,70 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#include "mt32emu.h" + +namespace MT32Emu { + +bool File::readBit16u(Bit16u *in) { + Bit8u b[2]; + if (read(&b[0], 2) != 2) + return false; + *in = ((b[0] << 8) | b[1]); + return true; +} + +bool File::readBit32u(Bit32u *in) { + Bit8u b[4]; + if (read(&b[0], 4) != 4) + return false; + *in = ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]); + return true; +} + +bool File::writeBit16u(Bit16u out) { + if (!writeBit8u((Bit8u)((out & 0xFF00) >> 8))) { + return false; + } + if (!writeBit8u((Bit8u)(out & 0x00FF))) { + return false; + } + return true; +} + +bool File::writeBit32u(Bit32u out) { + if (!writeBit8u((Bit8u)((out & 0xFF000000) >> 24))) { + return false; + } + if (!writeBit8u((Bit8u)((out & 0x00FF0000) >> 16))) { + return false; + } + if (!writeBit8u((Bit8u)((out & 0x0000FF00) >> 8))) { + return false; + } + if (!writeBit8u((Bit8u)(out & 0x000000FF))) { + return false; + } + return true; +} + +} // End of namespace MT32Emu + diff --git a/audio/softsynth/mt32/mt32_file.h b/audio/softsynth/mt32/mt32_file.h new file mode 100644 index 0000000000..e6641660ee --- /dev/null +++ b/audio/softsynth/mt32/mt32_file.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_FILE_H +#define MT32EMU_FILE_H + +#include "common/scummsys.h" + +namespace MT32Emu { + +class File { +public: + enum OpenMode { + OpenMode_read = 0, + OpenMode_write = 1 + }; + virtual ~File() {} + virtual void close() = 0; + virtual size_t read(void *in, size_t size) = 0; + virtual bool readBit8u(Bit8u *in) = 0; + virtual bool readBit16u(Bit16u *in); + virtual bool readBit32u(Bit32u *in); + virtual size_t write(const void *out, size_t size) = 0; + virtual bool writeBit8u(Bit8u out) = 0; + // Note: May write a single byte to the file before failing + virtual bool writeBit16u(Bit16u out); + // Note: May write some (<4) bytes to the file before failing + virtual bool writeBit32u(Bit32u out); + virtual bool isEOF() = 0; +}; + +} // End of namespace MT32Emu + +#endif diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h new file mode 100644 index 0000000000..6eedf04bc0 --- /dev/null +++ b/audio/softsynth/mt32/mt32emu.h @@ -0,0 +1,70 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_MT32EMU_H +#define MT32EMU_MT32EMU_H + +// Debugging +// Show the instruments played +#define MT32EMU_MONITOR_INSTRUMENTS 1 +// Shows number of partials MT-32 is playing, and on which parts +#define MT32EMU_MONITOR_PARTIALS 0 +// Determines how the waveform cache file is handled (must be regenerated after sampling rate change) +#define MT32EMU_WAVECACHEMODE 0 // Load existing cache if possible, otherwise generate and save cache +//#define MT32EMU_WAVECACHEMODE 1 // Load existing cache if possible, otherwise generate but don't save cache +//#define MT32EMU_WAVECACHEMODE 2 // Ignore existing cache, generate and save cache +//#define MT32EMU_WAVECACHEMODE 3 // Ignore existing cache, generate but don't save cache + +// Configuration +// The maximum number of partials playing simultaneously +#define MT32EMU_MAX_PARTIALS 32 +// The maximum number of notes playing simultaneously per part. +// No point making it more than MT32EMU_MAX_PARTIALS, since each note needs at least one partial. +#define MT32EMU_MAX_POLY 32 +// This calculates the exact frequencies of notes as they are played, instead of offsetting from pre-cached semitones. Potentially very slow. +#define MT32EMU_ACCURATENOTES 0 + +#if (defined (_MSC_VER) && defined(_M_IX86)) +#define MT32EMU_HAVE_X86 +#elif defined(__GNUC__) +#if __GNUC__ >= 3 && defined(__i386__) +#define MT32EMU_HAVE_X86 +#endif +#endif + +#ifdef MT32EMU_HAVE_X86 +#define MT32EMU_USE_MMX 1 +#else +#define MT32EMU_USE_MMX 0 +#endif + +#include "freeverb.h" + +#include "structures.h" +#include "i386.h" +#include "mt32_file.h" +#include "tables.h" +#include "partial.h" +#include "partialManager.h" +#include "part.h" +#include "synth.h" + +#endif diff --git a/audio/softsynth/mt32/part.cpp b/audio/softsynth/mt32/part.cpp new file mode 100644 index 0000000000..eb087f7ea0 --- /dev/null +++ b/audio/softsynth/mt32/part.cpp @@ -0,0 +1,633 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include + +#include "mt32emu.h" + +namespace MT32Emu { + +static const Bit8u PartialStruct[13] = { + 0, 0, 2, 2, 1, 3, + 3, 0, 3, 0, 2, 1, 3 }; + +static const Bit8u PartialMixStruct[13] = { + 0, 1, 0, 1, 1, 0, + 1, 3, 3, 2, 2, 2, 2 }; + +static const float floatKeyfollow[17] = { + -1.0f, -1.0f/2.0f, -1.0f/4.0f, 0.0f, + 1.0f/8.0f, 1.0f/4.0f, 3.0f/8.0f, 1.0f/2.0f, 5.0f/8.0f, 3.0f/4.0f, 7.0f/8.0f, 1.0f, + 5.0f/4.0f, 3.0f/2.0f, 2.0f, + 1.0009765625f, 1.0048828125f +}; + +//FIXME:KG: Put this dpoly stuff somewhere better +bool dpoly::isActive() const { + return partials[0] != NULL || partials[1] != NULL || partials[2] != NULL || partials[3] != NULL; +} + +Bit32u dpoly::getAge() const { + for (int i = 0; i < 4; i++) { + if (partials[i] != NULL) { + return partials[i]->age; + } + } + return 0; +} + +RhythmPart::RhythmPart(Synth *useSynth, unsigned int usePartNum): Part(useSynth, usePartNum) { + strcpy(name, "Rhythm"); + rhythmTemp = &synth->mt32ram.rhythmSettings[0]; + refresh(); +} + +Part::Part(Synth *useSynth, unsigned int usePartNum) { + this->synth = useSynth; + this->partNum = usePartNum; + patchCache[0].dirty = true; + holdpedal = false; + patchTemp = &synth->mt32ram.patchSettings[partNum]; + if (usePartNum == 8) { + // Nasty hack for rhythm + timbreTemp = NULL; + } else { + sprintf(name, "Part %d", partNum + 1); + timbreTemp = &synth->mt32ram.timbreSettings[partNum]; + } + currentInstr[0] = 0; + currentInstr[10] = 0; + expression = 127; + volumeMult = 0; + volumesetting.leftvol = 32767; + volumesetting.rightvol = 32767; + bend = 0.0f; + memset(polyTable,0,sizeof(polyTable)); + memset(patchCache, 0, sizeof(patchCache)); +} + +void Part::setHoldPedal(bool pedalval) { + if (holdpedal && !pedalval) { + holdpedal = false; + stopPedalHold(); + } else { + holdpedal = pedalval; + } +} + +void RhythmPart::setBend(unsigned int midiBend) { + synth->printDebug("%s: Setting bend (%d) not supported on rhythm", name, midiBend); + return; +} + +void Part::setBend(unsigned int midiBend) { + // FIXME:KG: Slightly unbalanced increments, but I wanted min -1.0, centre 0.0 and max 1.0 + if (midiBend <= 0x2000) { + bend = ((signed int)midiBend - 0x2000) / (float)0x2000; + } else { + bend = ((signed int)midiBend - 0x2000) / (float)0x1FFF; + } + // Loop through all partials to update their bend + for (int i = 0; i < MT32EMU_MAX_POLY; i++) { + for (int j = 0; j < 4; j++) { + if (polyTable[i].partials[j] != NULL) { + polyTable[i].partials[j]->setBend(bend); + } + } + } +} + +void RhythmPart::setModulation(unsigned int midiModulation) { + synth->printDebug("%s: Setting modulation (%d) not supported on rhythm", name, midiModulation); +} + +void Part::setModulation(unsigned int midiModulation) { + // Just a bloody guess, as always, before I get things figured out + for (int t = 0; t < 4; t++) { + if (patchCache[t].playPartial) { + int newrate = (patchCache[t].modsense * midiModulation) >> 7; + //patchCache[t].lfoperiod = lfotable[newrate]; + patchCache[t].lfodepth = newrate; + //FIXME:KG: timbreTemp->partial[t].lfo.depth = + } + } +} + +void RhythmPart::refresh() { + updateVolume(); + // (Re-)cache all the mapped timbres ahead of time + for (unsigned int drumNum = 0; drumNum < synth->controlROMMap->rhythmSettingsCount; drumNum++) { + int drumTimbreNum = rhythmTemp[drumNum].timbre; + if (drumTimbreNum >= 127) // 94 on MT-32 + continue; + Bit16s pan = rhythmTemp[drumNum].panpot; // They use R-L 0-14... + // FIXME:KG: Panning cache should be backed up to partials using it, too + if (pan < 7) { + drumPan[drumNum].leftvol = pan * 4681; + drumPan[drumNum].rightvol = 32767; + } else { + drumPan[drumNum].rightvol = (14 - pan) * 4681; + drumPan[drumNum].leftvol = 32767; + } + PatchCache *cache = drumCache[drumNum]; + backupCacheToPartials(cache); + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + cache[t].dirty = true; + cache[t].pitchShift = 0.0f; + cache[t].benderRange = 0.0f; + cache[t].pansetptr = &drumPan[drumNum]; + cache[t].reverb = rhythmTemp[drumNum].reverbSwitch > 0; + } + } +} + +void Part::refresh() { + updateVolume(); + backupCacheToPartials(patchCache); + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + patchCache[t].dirty = true; + patchCache[t].pitchShift = (patchTemp->patch.keyShift - 24) + (patchTemp->patch.fineTune - 50) / 100.0f; + patchCache[t].benderRange = patchTemp->patch.benderRange; + patchCache[t].pansetptr = &volumesetting; + patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0; + } + memcpy(currentInstr, timbreTemp->common.name, 10); +} + +const char *Part::getCurrentInstr() const { + return ¤tInstr[0]; +} + +void RhythmPart::refreshTimbre(unsigned int absTimbreNum) { + for (int m = 0; m < 85; m++) { + if (rhythmTemp[m].timbre == absTimbreNum - 128) + drumCache[m][0].dirty = true; + } +} + +void Part::refreshTimbre(unsigned int absTimbreNum) { + if (getAbsTimbreNum() == absTimbreNum) { + memcpy(currentInstr, timbreTemp->common.name, 10); + patchCache[0].dirty = true; + } +} + +int Part::fixBiaslevel(int srcpnt, int *dir) { + int noteat = srcpnt & 0x3F; + int outnote; + if (srcpnt < 64) + *dir = 0; + else + *dir = 1; + outnote = 33 + noteat; + //synth->printDebug("Bias note %d, dir %d", outnote, *dir); + + return outnote; +} + +int Part::fixKeyfollow(int srckey) { + if (srckey>=0 && srckey<=16) { + int keyfix[17] = { -256*16, -128*16, -64*16, 0, 32*16, 64*16, 96*16, 128*16, (128+32)*16, 192*16, (192+32)*16, 256*16, (256+64)*16, (256+128)*16, (512)*16, 4100, 4116}; + return keyfix[srckey]; + } else { + //LOG(LOG_ERROR|LOG_MISC,"Missed key: %d", srckey); + return 256; + } +} + +void Part::abortPoly(dpoly *poly) { + if (!poly->isPlaying) { + return; + } + for (int i = 0; i < 4; i++) { + Partial *partial = poly->partials[i]; + if (partial != NULL) { + partial->deactivate(); + } + } + poly->isPlaying = false; +} + +void Part::setPatch(const PatchParam *patch) { + patchTemp->patch = *patch; +} + +void RhythmPart::setTimbre(TimbreParam * /*timbre*/) { + synth->printDebug("%s: Attempted to call setTimbre() - doesn't make sense for rhythm", name); +} + +void Part::setTimbre(TimbreParam *timbre) { + *timbreTemp = *timbre; +} + +unsigned int RhythmPart::getAbsTimbreNum() const { + synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm", name); + return 0; +} + +unsigned int Part::getAbsTimbreNum() const { + return (patchTemp->patch.timbreGroup * 64) + patchTemp->patch.timbreNum; +} + +void RhythmPart::setProgram(unsigned int patchNum) { + synth->printDebug("%s: Attempt to set program (%d) on rhythm is invalid", name, patchNum); +} + +void Part::setProgram(unsigned int patchNum) { + setPatch(&synth->mt32ram.patches[patchNum]); + setTimbre(&synth->mt32ram.timbres[getAbsTimbreNum()].timbre); + + refresh(); + + allSoundOff(); //FIXME:KG: Is this correct? +} + +void Part::backupCacheToPartials(PatchCache cache[4]) { + // check if any partials are still playing with the old patch cache + // if so then duplicate the cached data from the part to the partial so that + // we can change the part's cache without affecting the partial. + // We delay this until now to avoid a copy operation with every note played + for (int m = 0; m < MT32EMU_MAX_POLY; m++) { + for (int i = 0; i < 4; i++) { + Partial *partial = polyTable[m].partials[i]; + if (partial != NULL && partial->patchCache == &cache[i]) { + partial->cachebackup = cache[i]; + partial->patchCache = &partial->cachebackup; + } + } + } +} + +void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) { + backupCacheToPartials(cache); + int partialCount = 0; + for (int t = 0; t < 4; t++) { + cache[t].PCMPartial = false; + if (((timbre->common.pmute >> t) & 0x1) == 1) { + cache[t].playPartial = true; + partialCount++; + } else { + cache[t].playPartial = false; + continue; + } + + // Calculate and cache common parameters + + cache[t].pcm = timbre->partial[t].wg.pcmwave; + cache[t].useBender = (timbre->partial[t].wg.bender == 1); + + switch (t) { + case 0: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x2) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; + cache[t].structurePosition = 0; + cache[t].structurePair = 1; + break; + case 1: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x1) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; + cache[t].structurePosition = 1; + cache[t].structurePair = 0; + break; + case 2: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x2) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; + cache[t].structurePosition = 0; + cache[t].structurePair = 3; + break; + case 3: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x1) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; + cache[t].structurePosition = 1; + cache[t].structurePair = 2; + break; + default: + break; + } + + cache[t].waveform = timbre->partial[t].wg.waveform; + cache[t].pulsewidth = timbre->partial[t].wg.pulsewid; + cache[t].pwsens = timbre->partial[t].wg.pwvelo; + if (timbre->partial[t].wg.keyfollow > 16) { + synth->printDebug("Bad keyfollow value in timbre!"); + cache[t].pitchKeyfollow = 1.0f; + } else { + cache[t].pitchKeyfollow = floatKeyfollow[timbre->partial[t].wg.keyfollow]; + } + + cache[t].pitch = timbre->partial[t].wg.coarse + (timbre->partial[t].wg.fine - 50) / 100.0f + 24.0f; + cache[t].pitchEnv = timbre->partial[t].env; + cache[t].pitchEnv.sensitivity = (char)((float)cache[t].pitchEnv.sensitivity * 1.27f); + cache[t].pitchsustain = cache[t].pitchEnv.level[3]; + + // Calculate and cache TVA envelope stuff + cache[t].ampEnv = timbre->partial[t].tva; + cache[t].ampEnv.level = (char)((float)cache[t].ampEnv.level * 1.27f); + + cache[t].ampbias[0] = fixBiaslevel(cache[t].ampEnv.biaspoint1, &cache[t].ampdir[0]); + cache[t].ampblevel[0] = 12 - cache[t].ampEnv.biaslevel1; + cache[t].ampbias[1] = fixBiaslevel(cache[t].ampEnv.biaspoint2, &cache[t].ampdir[1]); + cache[t].ampblevel[1] = 12 - cache[t].ampEnv.biaslevel2; + cache[t].ampdepth = cache[t].ampEnv.envvkf * cache[t].ampEnv.envvkf; + + // Calculate and cache filter stuff + cache[t].filtEnv = timbre->partial[t].tvf; + cache[t].filtkeyfollow = fixKeyfollow(cache[t].filtEnv.keyfollow); + cache[t].filtEnv.envdepth = (char)((float)cache[t].filtEnv.envdepth * 1.27); + cache[t].tvfbias = fixBiaslevel(cache[t].filtEnv.biaspoint, &cache[t].tvfdir); + cache[t].tvfblevel = cache[t].filtEnv.biaslevel; + cache[t].filtsustain = cache[t].filtEnv.envlevel[3]; + + // Calculate and cache LFO stuff + cache[t].lfodepth = timbre->partial[t].lfo.depth; + cache[t].lfoperiod = synth->tables.lfoPeriod[(int)timbre->partial[t].lfo.rate]; + cache[t].lforate = timbre->partial[t].lfo.rate; + cache[t].modsense = timbre->partial[t].lfo.modsense; + } + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + cache[t].dirty = false; + cache[t].partialCount = partialCount; + cache[t].sustain = (timbre->common.nosustain == 0); + } + //synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", cache[0].waveform, cache[1].waveform, cache[2].waveform, cache[3].waveform); + +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): Recached timbre", name, currentInstr); + for (int i = 0; i < 4; i++) { + synth->printDebug(" %d: play=%s, pcm=%s (%d), wave=%d", i, cache[i].playPartial ? "YES" : "NO", cache[i].PCMPartial ? "YES" : "NO", timbre->partial[i].wg.pcmwave, timbre->partial[i].wg.waveform); + } +#endif +} + +const char *Part::getName() const { + return name; +} + +void Part::updateVolume() { + volumeMult = synth->tables.volumeMult[patchTemp->outlevel * expression / 127]; +} + +int Part::getVolume() const { + // FIXME: Use the mappings for this in the control ROM + return patchTemp->outlevel * 127 / 100; +} + +void Part::setVolume(int midiVolume) { + // FIXME: Use the mappings for this in the control ROM + patchTemp->outlevel = (Bit8u)(midiVolume * 100 / 127); + updateVolume(); + synth->printDebug("%s (%s): Set volume to %d", name, currentInstr, midiVolume); +} + +void Part::setExpression(int midiExpression) { + expression = midiExpression; + updateVolume(); +} + +void RhythmPart::setPan(unsigned int midiPan) +{ + // FIXME:KG: This is unchangeable for drums (they always use drumPan), is that correct? + synth->printDebug("%s: Setting pan (%d) not supported on rhythm", name, midiPan); +} + +void Part::setPan(unsigned int midiPan) { + // FIXME:KG: Tweaked this a bit so that we have a left 100%, centre and right 100% + // (But this makes the range somewhat skewed) + // Check against the real thing + // NOTE: Panning is inverted compared to GM. + if (midiPan < 64) { + volumesetting.leftvol = (Bit16s)(midiPan * 512); + volumesetting.rightvol = 32767; + } else if (midiPan == 64) { + volumesetting.leftvol = 32767; + volumesetting.rightvol = 32767; + } else { + volumesetting.rightvol = (Bit16s)((127 - midiPan) * 520); + volumesetting.leftvol = 32767; + } + patchTemp->panpot = (Bit8u)(midiPan * 14 / 127); + //synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot); +} + +void RhythmPart::playNote(unsigned int key, int vel) { + if (key < 24 || key > 108)/*> 87 on MT-32)*/ { + synth->printDebug("%s: Attempted to play invalid key %d", name, key); + return; + } + int drumNum = key - 24; + int drumTimbreNum = rhythmTemp[drumNum].timbre; + if (drumTimbreNum >= 127) { // 94 on MT-32 + synth->printDebug("%s: Attempted to play unmapped key %d", name, key); + return; + } + int absTimbreNum = drumTimbreNum + 128; + TimbreParam *timbre = &synth->mt32ram.timbres[absTimbreNum].timbre; + memcpy(currentInstr, timbre->common.name, 10); +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): starting poly (drum %d, timbre %d) - Vel %d Key %d", name, currentInstr, drumNum, absTimbreNum, vel, key); +#endif + if (drumCache[drumNum][0].dirty) { + cacheTimbre(drumCache[drumNum], timbre); + } + playPoly(drumCache[drumNum], key, MIDDLEC, vel); +} + +void Part::playNote(unsigned int key, int vel) { + int freqNum = key; + if (freqNum < 12) { + synth->printDebug("%s (%s): Attempted to play invalid key %d < 12; moving up by octave", name, currentInstr, key); + freqNum += 12; + } else if (freqNum > 108) { + synth->printDebug("%s (%s): Attempted to play invalid key %d > 108; moving down by octave", name, currentInstr, key); + while (freqNum > 108) { + freqNum -= 12; + } + } + // POLY1 mode, Single Assign + // Haven't found any software that uses any of the other poly modes + // FIXME:KG: Should this also apply to rhythm? + for (unsigned int i = 0; i < MT32EMU_MAX_POLY; i++) { + if (polyTable[i].isActive() && (polyTable[i].key == key)) { + //AbortPoly(&polyTable[i]); + stopNote(key); + break; + } + } +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): starting poly - Vel %d Key %d", name, currentInstr, vel, key); +#endif + if (patchCache[0].dirty) { + cacheTimbre(patchCache, timbreTemp); + } + playPoly(patchCache, key, freqNum, vel); +} + +void Part::playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel) { + unsigned int needPartials = cache[0].partialCount; + unsigned int freePartials = synth->partialManager->getFreePartialCount(); + + if (freePartials < needPartials) { + if (!synth->partialManager->freePartials(needPartials - freePartials, partNum)) { + synth->printDebug("%s (%s): Insufficient free partials to play key %d (vel=%d); needed=%d, free=%d", name, currentInstr, key, vel, needPartials, synth->partialManager->getFreePartialCount()); + return; + } + } + // Find free poly + int m; + for (m = 0; m < MT32EMU_MAX_POLY; m++) { + if (!polyTable[m].isActive()) { + break; + } + } + if (m == MT32EMU_MAX_POLY) { + synth->printDebug("%s (%s): No free poly to play key %d (vel %d)", name, currentInstr, key, vel); + return; + } + + dpoly *tpoly = &polyTable[m]; + + tpoly->isPlaying = true; + tpoly->key = key; + tpoly->isDecay = false; + tpoly->freqnum = freqNum; + tpoly->vel = vel; + tpoly->pedalhold = false; + + bool allnull = true; + for (int x = 0; x < 4; x++) { + if (cache[x].playPartial) { + tpoly->partials[x] = synth->partialManager->allocPartial(partNum); + allnull = false; + } else { + tpoly->partials[x] = NULL; + } + } + + if (allnull) + synth->printDebug("%s (%s): No partials to play for this instrument", name, this->currentInstr); + + tpoly->sustain = cache[0].sustain; + tpoly->volumeptr = &volumeMult; + + for (int x = 0; x < 4; x++) { + if (tpoly->partials[x] != NULL) { + tpoly->partials[x]->startPartial(tpoly, &cache[x], tpoly->partials[cache[x].structurePair]); + tpoly->partials[x]->setBend(bend); + } + } +} + +static void startDecayPoly(dpoly *tpoly) { + if (tpoly->isDecay) { + return; + } + tpoly->isDecay = true; + + for (int t = 0; t < 4; t++) { + Partial *partial = tpoly->partials[t]; + if (partial == NULL) + continue; + partial->startDecayAll(); + } + tpoly->isPlaying = false; +} + +void Part::allNotesOff() { + // Note: Unchecked on real MT-32, but the MIDI specification states that all notes off (0x7B) + // should treat the hold pedal as usual. + // All *sound* off (0x78) should stop notes immediately regardless of the hold pedal. + // The latter controller is not implemented on the MT-32 (according to the docs). + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying) { + if (holdpedal) + tpoly->pedalhold = true; + else if (tpoly->sustain) + startDecayPoly(tpoly); + } + } +} + +void Part::allSoundOff() { + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying) { + startDecayPoly(tpoly); + } + } +} + +void Part::stopPedalHold() { + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly; + tpoly = &polyTable[q]; + if (tpoly->isActive() && tpoly->pedalhold) + stopNote(tpoly->key); + } +} + +void Part::stopNote(unsigned int key) { + // Non-sustaining instruments ignore stop commands. + // They die away eventually anyway + +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key); +#endif + + if (key != 255) { + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying && tpoly->key == key) { + if (holdpedal) + tpoly->pedalhold = true; + else if (tpoly->sustain) + startDecayPoly(tpoly); + } + } + return; + } + + // Find oldest poly... yes, the MT-32 can be reconfigured to kill different poly first + // This is simplest + int oldest = -1; + Bit32u oldage = 0; + + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + + if (tpoly->isPlaying && !tpoly->isDecay) { + if (tpoly->getAge() >= oldage) { + oldage = tpoly->getAge(); + oldest = q; + } + } + } + + if (oldest != -1) { + startDecayPoly(&polyTable[oldest]); + } +} + +} diff --git a/audio/softsynth/mt32/part.h b/audio/softsynth/mt32/part.h new file mode 100644 index 0000000000..54c4999653 --- /dev/null +++ b/audio/softsynth/mt32/part.h @@ -0,0 +1,113 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_PART_H +#define MT32EMU_PART_H + +namespace MT32Emu { + +class PartialManager; +class Synth; + +class Part { +private: + // Pointers to the areas of the MT-32's memory dedicated to this part (for parts 1-8) + MemParams::PatchTemp *patchTemp; + TimbreParam *timbreTemp; + + // 0=Part 1, .. 7=Part 8, 8=Rhythm + unsigned int partNum; + + bool holdpedal; + + StereoVolume volumesetting; + + PatchCache patchCache[4]; + + float bend; // -1.0 .. +1.0 + + dpoly polyTable[MT32EMU_MAX_POLY]; + + void abortPoly(dpoly *poly); + + static int fixKeyfollow(int srckey); + static int fixBiaslevel(int srcpnt, int *dir); + + void setPatch(const PatchParam *patch); + +protected: + Synth *synth; + char name[8]; // "Part 1".."Part 8", "Rhythm" + char currentInstr[11]; + int expression; + Bit32u volumeMult; + + void updateVolume(); + void backupCacheToPartials(PatchCache cache[4]); + void cacheTimbre(PatchCache cache[4], const TimbreParam *timbre); + void playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel); + const char *getName() const; + +public: + Part(Synth *synth, unsigned int usePartNum); + virtual ~Part() {} + virtual void playNote(unsigned int key, int vel); + void stopNote(unsigned int key); + void allNotesOff(); + void allSoundOff(); + int getVolume() const; + void setVolume(int midiVolume); + void setExpression(int midiExpression); + virtual void setPan(unsigned int midiPan); + virtual void setBend(unsigned int midiBend); + virtual void setModulation(unsigned int midiModulation); + virtual void setProgram(unsigned int midiProgram); + void setHoldPedal(bool pedalval); + void stopPedalHold(); + virtual void refresh(); + virtual void refreshTimbre(unsigned int absTimbreNum); + virtual void setTimbre(TimbreParam *timbre); + virtual unsigned int getAbsTimbreNum() const; + const char *getCurrentInstr() const; +}; + +class RhythmPart: public Part { + // Pointer to the area of the MT-32's memory dedicated to rhythm + const MemParams::RhythmTemp *rhythmTemp; + + // This caches the timbres/settings in use by the rhythm part + PatchCache drumCache[85][4]; + StereoVolume drumPan[85]; +public: + RhythmPart(Synth *synth, unsigned int usePartNum); + void refresh(); + void refreshTimbre(unsigned int timbreNum); + void setTimbre(TimbreParam *timbre); + void playNote(unsigned int key, int vel); + unsigned int getAbsTimbreNum() const; + void setPan(unsigned int midiPan); + void setBend(unsigned int midiBend); + void setModulation(unsigned int midiModulation); + void setProgram(unsigned int patchNum); +}; + +} +#endif diff --git a/audio/softsynth/mt32/partial.cpp b/audio/softsynth/mt32/partial.cpp new file mode 100644 index 0000000000..5ba9ef6145 --- /dev/null +++ b/audio/softsynth/mt32/partial.cpp @@ -0,0 +1,968 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "mt32emu.h" + +#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__) +// Older versions of Mac OS X didn't supply a powf function, so using it +// will cause a binary incompatibility when trying to run a binary built +// on a newer OS X release on an olderr one. And Solaris 8 doesn't provide +// powf, floorf, fabsf etc. at all. +// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in +// libstdc++. math/stubs.o should be empty, but it comes with a symbol for +// powf, resulting in a linker error because of multiple definitions. +// Hence we re-define them here. The only potential drawback is that it +// might be a little bit slower this way. +#define powf(x,y) ((float)pow(x,y)) +#define floorf(x) ((float)floor(x)) +#define fabsf(x) ((float)fabs(x)) +#endif + +#define FIXEDPOINT_UDIV(x, y, point) (((x) << (point)) / ((y))) +#define FIXEDPOINT_SDIV(x, y, point) (((x) * (1 << point)) / ((y))) +#define FIXEDPOINT_UMULT(x, y, point) (((x) * (y)) >> point) +#define FIXEDPOINT_SMULT(x, y, point) (((x) * (y)) / (1 << point)) + +using namespace MT32Emu; + +Partial::Partial(Synth *useSynth) { + this->synth = useSynth; + ownerPart = -1; + poly = NULL; + pair = NULL; +#if MT32EMU_ACCURATENOTES == 1 + for (int i = 0; i < 3; i++) { + noteLookupStorage.waveforms[i] = new Bit16s[65536]; + } + noteLookup = ¬eLookupStorage; +#endif +} + +Partial::~Partial() { +#if MT32EMU_ACCURATENOTES == 1 + for (int i = 0; i < 3; i++) { + delete[] noteLookupStorage.waveforms[i]; + } + delete[] noteLookupStorage.wavTable; +#endif +} + +int Partial::getOwnerPart() const { + return ownerPart; +} + +bool Partial::isActive() { + return ownerPart > -1; +} + +const dpoly *Partial::getDpoly() const { + return this->poly; +} + +void Partial::activate(int part) { + // This just marks the partial as being assigned to a part + ownerPart = part; +} + +void Partial::deactivate() { + ownerPart = -1; + if (poly != NULL) { + for (int i = 0; i < 4; i++) { + if (poly->partials[i] == this) { + poly->partials[i] = NULL; + break; + } + } + if (pair != NULL) { + pair->pair = NULL; + } + } +} + +void Partial::initKeyFollow(int key) { + // Setup partial keyfollow + // Note follow relative to middle C + + // Calculate keyfollow for pitch +#if 1 + float rel = key == -1 ? 0.0f : (key - MIDDLEC); + float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch + patchCache->pitchShift; + //FIXME:KG: Does it truncate the keyfollowed pitch to a semitone (towards MIDDLEC)? + //int newKey = (int)(rel * patchCache->pitchKeyfollow); + //float newPitch = newKey + patchCache->pitch + patchCache->pitchShift; +#else + float rel = key == -1 ? 0.0f : (key + patchCache->pitchShift - MIDDLEC); + float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch; +#endif +#if MT32EMU_ACCURATENOTES == 1 + noteVal = newPitch; + synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f", key, (double)patchCache->pitch, (double)patchCache->pitchKeyfollow, (double)patchCache->pitchShift, (double)newPitch); +#else + float newPitchInt; + float newPitchFract = modff(newPitch, &newPitchInt); + if (newPitchFract > 0.5f) { + newPitchInt += 1.0f; + newPitchFract -= 1.0f; + } + noteVal = (int)newPitchInt; + fineShift = (int)(powf(2.0f, newPitchFract / 12.0f) * 4096.0f); + synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f, noteVal=%d, fineShift=%d", key, (double)patchCache->pitch, (double)patchCache->pitchKeyfollow, (double)patchCache->pitchShift, (double)newPitch, noteVal, fineShift); +#endif + // FIXME:KG: Raise/lower by octaves until in the supported range. + while (noteVal > HIGHEST_NOTE) // FIXME:KG: see tables.cpp: >108? + noteVal -= 12; + while (noteVal < LOWEST_NOTE) // FIXME:KG: see tables.cpp: <12? + noteVal += 12; + // Calculate keyfollow for filter + int keyfollow = ((key - MIDDLEC) * patchCache->filtkeyfollow) / 4096; + if (keyfollow > 108) + keyfollow = 108; + else if (keyfollow < -108) + keyfollow = -108; + filtVal = synth->tables.tvfKeyfollowMult[keyfollow + 108]; + realVal = synth->tables.tvfKeyfollowMult[(noteVal - MIDDLEC) + 108]; +} + +int Partial::getKey() const { + if (poly == NULL) { + return -1; + } else { + return poly->key; + } +} + +void Partial::startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial) { + if (usePoly == NULL || useCache == NULL) { + synth->printDebug("*** Error: Starting partial for owner %d, usePoly=%s, useCache=%s", ownerPart, usePoly == NULL ? "*** NULL ***" : "OK", useCache == NULL ? "*** NULL ***" : "OK"); + return; + } + patchCache = useCache; + poly = usePoly; + mixType = patchCache->structureMix; + structurePosition = patchCache->structurePosition; + + play = true; + initKeyFollow(poly->freqnum); // Initialises noteVal, filtVal and realVal +#if MT32EMU_ACCURATENOTES == 0 + noteLookup = &synth->tables.noteLookups[noteVal - LOWEST_NOTE]; +#else + Tables::initNote(synth, ¬eLookupStorage, noteVal, (float)synth->myProp.sampleRate, synth->masterTune, synth->pcmWaves, NULL); +#endif + keyLookup = &synth->tables.keyLookups[poly->freqnum - 12]; + + if (patchCache->PCMPartial) { + pcmNum = patchCache->pcm; + if (synth->controlROMMap->pcmCount > 128) { + // CM-32L, etc. support two "banks" of PCMs, selectable by waveform type parameter. + if (patchCache->waveform > 1) { + pcmNum += 128; + } + } + pcmWave = &synth->pcmWaves[pcmNum]; + } else { + pcmWave = NULL; + } + + lfoPos = 0; + pulsewidth = patchCache->pulsewidth + synth->tables.pwVelfollowAdd[patchCache->pwsens][poly->vel]; + if (pulsewidth > 100) { + pulsewidth = 100; + } else if (pulsewidth < 0) { + pulsewidth = 0; + } + + for (int e = 0; e < 3; e++) { + envs[e].envpos = 0; + envs[e].envstat = -1; + envs[e].envbase = 0; + envs[e].envdist = 0; + envs[e].envsize = 0; + envs[e].sustaining = false; + envs[e].decaying = false; + envs[e].prevlevel = 0; + envs[e].counter = 0; + envs[e].count = 0; + } + ampEnvVal = 0; + pitchEnvVal = 0; + pitchSustain = false; + loopPos = 0; + partialOff.pcmoffset = partialOff.pcmplace = 0; + pair = pairPartial; + useNoisePair = pairPartial == NULL && (mixType == 1 || mixType == 2); + age = 0; + alreadyOutputed = false; + memset(history,0,sizeof(history)); +} + +Bit16s *Partial::generateSamples(long length) { + if (!isActive() || alreadyOutputed) { + return NULL; + } + if (poly == NULL) { + synth->printDebug("*** ERROR: poly is NULL at Partial::generateSamples()!"); + return NULL; + } + + alreadyOutputed = true; + + // Generate samples + + Bit16s *partialBuf = &myBuffer[0]; + Bit32u volume = *poly->volumeptr; + while (length--) { + Bit32s envval; + Bit32s sample = 0; + if (!envs[EnvelopeType_amp].sustaining) { + if (envs[EnvelopeType_amp].count <= 0) { + Bit32u ampval = getAmpEnvelope(); + if (!play) { + deactivate(); + break; + } + if (ampval > 100) { + ampval = 100; + } + + ampval = synth->tables.volumeMult[ampval]; + ampval = FIXEDPOINT_UMULT(ampval, synth->tables.tvaVelfollowMult[poly->vel][(int)patchCache->ampEnv.velosens], 8); + //if (envs[EnvelopeType_amp].sustaining) + ampEnvVal = ampval; + } + --envs[EnvelopeType_amp].count; + } + + unsigned int lfoShift = 0x1000; + if (pitchSustain) { + // Calculate LFO position + // LFO does not kick in completely until pitch envelope sustains + if (patchCache->lfodepth > 0) { + lfoPos++; + if (lfoPos >= patchCache->lfoperiod) + lfoPos = 0; + int lfoatm = FIXEDPOINT_UDIV(lfoPos, patchCache->lfoperiod, 16); + int lfoatr = synth->tables.sintable[lfoatm]; + lfoShift = synth->tables.lfoShift[patchCache->lfodepth][lfoatr]; + } + } else { + // Calculate Pitch envelope + envval = getPitchEnvelope(); + int pd = patchCache->pitchEnv.depth; + pitchEnvVal = synth->tables.pitchEnvVal[pd][envval]; + } + + int delta; + + // Wrap positions or end if necessary + if (patchCache->PCMPartial) { + // PCM partial + + delta = noteLookup->wavTable[pcmNum]; + int len = pcmWave->len; + if (partialOff.pcmplace >= len) { + if (pcmWave->loop) { + //partialOff.pcmplace = partialOff.pcmoffset = 0; + partialOff.pcmplace %= len; + } else { + play = false; + deactivate(); + break; + } + } + } else { + // Synthesis partial + delta = 0x10000; + partialOff.pcmplace %= (Bit16u)noteLookup->div2; + } + + // Build delta for position of next sample + // Fix delta code + Bit32u tdelta = delta; +#if MT32EMU_ACCURATENOTES == 0 + tdelta = FIXEDPOINT_UMULT(tdelta, fineShift, 12); +#endif + tdelta = FIXEDPOINT_UMULT(tdelta, pitchEnvVal, 12); + tdelta = FIXEDPOINT_UMULT(tdelta, lfoShift, 12); + tdelta = FIXEDPOINT_UMULT(tdelta, bendShift, 12); + delta = (int)tdelta; + + // Get waveform - either PCM or synthesized sawtooth or square + if (ampEnvVal > 0) { + if (patchCache->PCMPartial) { + // Render PCM sample + int ra, rb, dist; + Bit32u taddr; + Bit32u pcmAddr = pcmWave->addr; + if (delta < 0x10000) { + // Linear sound interpolation + taddr = pcmAddr + partialOff.pcmplace; + ra = synth->pcmROMData[taddr]; + taddr++; + if (taddr == pcmAddr + pcmWave->len) { + // Past end of PCM + if (pcmWave->loop) { + rb = synth->pcmROMData[pcmAddr]; + } else { + rb = 0; + } + } else { + rb = synth->pcmROMData[taddr]; + } + dist = rb - ra; + sample = (ra + ((dist * (Bit32s)(partialOff.pcmoffset >> 8)) >> 8)); + } else { + // Sound decimation + // The right way to do it is to use a lowpass filter on the waveform before selecting + // a point. This is too slow. The following approximates this as fast as possible + int idelta = delta >> 16; + taddr = pcmAddr + partialOff.pcmplace; + ra = synth->pcmROMData[taddr++]; + for (int ix = 0; ix < idelta - 1; ix++) { + if (taddr == pcmAddr + pcmWave->len) { + // Past end of PCM + if (pcmWave->loop) { + taddr = pcmAddr; + } else { + // Behave as if all subsequent samples were 0 + break; + } + } + ra += synth->pcmROMData[taddr++]; + } + sample = ra / idelta; + } + } else { + // Render synthesised sample + int toff = partialOff.pcmplace; + int minorplace = partialOff.pcmoffset >> 14; + Bit32s filterInput; + Bit32s filtval = getFiltEnvelope(); + + //synth->printDebug("Filtval: %d", filtval); + + if ((patchCache->waveform & 1) == 0) { + // Square waveform. Made by combining two pregenerated bandlimited + // sawtooth waveforms + Bit32u ofsA = ((toff << 2) + minorplace) % noteLookup->waveformSize[0]; + int width = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[pulsewidth], 7); + Bit32u ofsB = (ofsA + width) % noteLookup->waveformSize[0]; + Bit16s pa = noteLookup->waveforms[0][ofsA]; + Bit16s pb = noteLookup->waveforms[0][ofsB]; + filterInput = pa - pb; + // Non-bandlimited squarewave + /* + ofs = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 8); + if (toff < ofs) + sample = 1 * WGAMP; + else + sample = -1 * WGAMP; + */ + } else { + // Sawtooth. Made by combining the full cosine and half cosine according + // to how it looks on the MT-32. What it really does it takes the + // square wave and multiplies it by a full cosine + int waveoff = (toff << 2) + minorplace; + if (toff < noteLookup->sawTable[pulsewidth]) + filterInput = noteLookup->waveforms[1][waveoff % noteLookup->waveformSize[1]]; + else + filterInput = noteLookup->waveforms[2][waveoff % noteLookup->waveformSize[2]]; + // This is the correct way + // Seems slow to me (though bandlimited) -- doesn't seem to + // sound any better though + /* + //int pw = (patchCache->pulsewidth * pulsemod[filtval]) >> 8; + + Bit32u ofs = toff % (noteLookup->div2 >> 1); + + Bit32u ofs3 = toff + FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 9); + ofs3 = ofs3 % (noteLookup->div2 >> 1); + + pa = noteLookup->waveforms[0][ofs]; + pb = noteLookup->waveforms[0][ofs3]; + sample = ((pa - pb) * noteLookup->waveforms[2][toff]) / 2; + */ + } + + //Very exact filter + if (filtval > ((FILTERGRAN * 15) / 16)) + filtval = ((FILTERGRAN * 15) / 16); + sample = (Bit32s)(floorf((synth->iirFilter)((float)filterInput, &history[0], synth->tables.filtCoeff[filtval][(int)patchCache->filtEnv.resonance])) / synth->tables.resonanceFactor[patchCache->filtEnv.resonance]); + if (sample < -32768) { + synth->printDebug("Overdriven amplitude for %d: %d:=%d < -32768", patchCache->waveform, filterInput, sample); + sample = -32768; + } + else if (sample > 32767) { + synth->printDebug("Overdriven amplitude for %d: %d:=%d > 32767", patchCache->waveform, filterInput, sample); + sample = 32767; + } + } + } + + // Add calculated delta to our waveform offset + Bit32u absOff = ((partialOff.pcmplace << 16) | partialOff.pcmoffset); + absOff += delta; + partialOff.pcmplace = (Bit16u)((absOff & 0xFFFF0000) >> 16); + partialOff.pcmoffset = (Bit16u)(absOff & 0xFFFF); + + // Put volume envelope over generated sample + sample = FIXEDPOINT_SMULT(sample, ampEnvVal, 9); + sample = FIXEDPOINT_SMULT(sample, volume, 7); + envs[EnvelopeType_amp].envpos++; + envs[EnvelopeType_pitch].envpos++; + envs[EnvelopeType_filt].envpos++; + + *partialBuf++ = (Bit16s)sample; + } + // We may have deactivated and broken out of the loop before the end of the buffer, + // if so then fill the remainder with 0s. + if (++length > 0) + memset(partialBuf, 0, length * 2); + return &myBuffer[0]; +} + +void Partial::setBend(float factor) { + if (!patchCache->useBender || factor == 0.0f) { + bendShift = 4096; + return; + } + // NOTE:KG: We can't do this smoothly with lookup tables, unless we use several MB. + // FIXME:KG: Bend should be influenced by pitch key-follow too, according to docs. + float bendSemitones = factor * patchCache->benderRange; // -24 .. 24 + float mult = powf(2.0f, bendSemitones / 12.0f); + synth->printDebug("setBend(): factor=%f, benderRange=%f, semitones=%f, mult=%f\n", (double)factor, (double)patchCache->benderRange, (double)bendSemitones, (double)mult); + bendShift = (int)(mult * 4096.0f); +} + +Bit16s *Partial::mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) + return buf2; + if (buf2 == NULL) + return buf1; + + Bit16s *outBuf = buf1; +#if MT32EMU_USE_MMX >= 1 + // KG: This seems to be fine + int donelen = i386_mixBuffers(buf1, buf2, len); + len -= donelen; + buf1 += donelen; + buf2 += donelen; +#endif + while (len--) { + *buf1 = *buf1 + *buf2; + buf1++, buf2++; + } + return outBuf; +} + +Bit16s *Partial::mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) + return NULL; + if (buf2 == NULL) { + Bit16s *outBuf = buf1; + while (len--) { + if (*buf1 < -8192) + *buf1 = -8192; + else if (*buf1 > 8192) + *buf1 = 8192; + buf1++; + } + return outBuf; + } + + Bit16s *outBuf = buf1; +#if MT32EMU_USE_MMX >= 1 + // KG: This seems to be fine + int donelen = i386_mixBuffersRingMix(buf1, buf2, len); + len -= donelen; + buf1 += donelen; + buf2 += donelen; +#endif + while (len--) { + float a, b; + a = ((float)*buf1) / 8192.0f; + b = ((float)*buf2) / 8192.0f; + a = (a * b) + a; + if (a > 1.0f) + a = 1.0f; + if (a < -1.0f) + a = -1.0f; + *buf1 = (Bit16s)(a * 8192.0f); + buf1++; + buf2++; + //buf1[i] = (Bit16s)(((Bit32s)buf1[i] * (Bit32s)buf2[i]) >> 10) + buf1[i]; + } + return outBuf; +} + +Bit16s *Partial::mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) { + return NULL; + } + if (buf2 == NULL) { + return NULL; + } + + Bit16s *outBuf = buf1; +#if MT32EMU_USE_MMX >= 1 + // FIXME:KG: Not really checked as working + int donelen = i386_mixBuffersRing(buf1, buf2, len); + len -= donelen; + buf1 += donelen; + buf2 += donelen; +#endif + while (len--) { + float a, b; + a = ((float)*buf1) / 8192.0f; + b = ((float)*buf2) / 8192.0f; + a *= b; + if (a > 1.0f) + a = 1.0f; + if (a < -1.0f) + a = -1.0f; + *buf1 = (Bit16s)(a * 8192.0f); + buf1++; + buf2++; + } + return outBuf; +} + +void Partial::mixBuffersStereo(Bit16s *buf1, Bit16s *buf2, Bit16s *outBuf, int len) { + if (buf2 == NULL) { + while (len--) { + *outBuf++ = *buf1++; + *outBuf++ = 0; + } + } else if (buf1 == NULL) { + while (len--) { + *outBuf++ = 0; + *outBuf++ = *buf2++; + } + } else { + while (len--) { + *outBuf++ = *buf1++; + *outBuf++ = *buf2++; + } + } +} + +bool Partial::produceOutput(Bit16s *partialBuf, long length) { + if (!isActive() || alreadyOutputed) + return false; + if (poly == NULL) { + synth->printDebug("*** ERROR: poly is NULL at Partial::produceOutput()!"); + return false; + } + + Bit16s *pairBuf = NULL; + // Check for dependant partial + if (pair != NULL) { + if (!pair->alreadyOutputed) { + // Note: pair may have become NULL after this + pairBuf = pair->generateSamples(length); + } + } else if (useNoisePair) { + // Generate noise for pairless ring mix + pairBuf = synth->tables.noiseBuf; + } + + Bit16s *myBuf = generateSamples(length); + + if (myBuf == NULL && pairBuf == NULL) + return false; + + Bit16s *p1buf, *p2buf; + + if (structurePosition == 0 || pairBuf == NULL) { + p1buf = myBuf; + p2buf = pairBuf; + } else { + p2buf = myBuf; + p1buf = pairBuf; + } + + //synth->printDebug("mixType: %d", mixType); + + Bit16s *mixedBuf; + switch (mixType) { + case 0: + // Standard sound mix + mixedBuf = mixBuffers(p1buf, p2buf, length); + break; + + case 1: + // Ring modulation with sound mix + mixedBuf = mixBuffersRingMix(p1buf, p2buf, length); + break; + + case 2: + // Ring modulation alone + mixedBuf = mixBuffersRing(p1buf, p2buf, length); + break; + + case 3: + // Stereo mixing. One partial to one speaker channel, one to another. + // FIXME:KG: Surely we should be multiplying by the left/right volumes here? + mixBuffersStereo(p1buf, p2buf, partialBuf, length); + return true; + + default: + mixedBuf = mixBuffers(p1buf, p2buf, length); + break; + } + + if (mixedBuf == NULL) + return false; + + Bit16s leftvol, rightvol; + leftvol = patchCache->pansetptr->leftvol; + rightvol = patchCache->pansetptr->rightvol; + +#if MT32EMU_USE_MMX >= 2 + // FIXME:KG: This appears to introduce crackle + int donelen = i386_partialProductOutput(length, leftvol, rightvol, partialBuf, mixedBuf); + length -= donelen; + mixedBuf += donelen; + partialBuf += donelen * 2; +#endif + while (length--) { + *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)leftvol) >> 15); + *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)rightvol) >> 15); + mixedBuf++; + } + return true; +} + +Bit32s Partial::getFiltEnvelope() { + int reshigh; + + int cutoff, depth; + + EnvelopeStatus *tStat = &envs[EnvelopeType_filt]; + + if (tStat->decaying) { + reshigh = tStat->envbase; + reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + if (tStat->envpos >= tStat->envsize) + reshigh = 0; + } else { + if (tStat->envstat==4) { + reshigh = patchCache->filtsustain; + if (!poly->sustain) { + startDecay(EnvelopeType_filt, reshigh); + } + } else { + if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { + if (tStat->envstat==-1) + tStat->envbase = 0; + else + tStat->envbase = patchCache->filtEnv.envlevel[tStat->envstat]; + tStat->envstat++; + tStat->envpos = 0; + if (tStat->envstat == 3) { + tStat->envsize = synth->tables.envTime[(int)patchCache->filtEnv.envtime[tStat->envstat]]; + } else { + Bit32u envTime = (int)patchCache->filtEnv.envtime[tStat->envstat]; + if (tStat->envstat > 1) { + int envDiff = abs(patchCache->filtEnv.envlevel[tStat->envstat] - patchCache->filtEnv.envlevel[tStat->envstat - 1]); + if (envTime > synth->tables.envDeltaMaxTime[envDiff]) { + envTime = synth->tables.envDeltaMaxTime[envDiff]; + } + } + + tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf]) >> 8; + } + + tStat->envsize++; + tStat->envdist = patchCache->filtEnv.envlevel[tStat->envstat] - tStat->envbase; + } + + reshigh = tStat->envbase; + reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + + } + tStat->prevlevel = reshigh; + } + + cutoff = patchCache->filtEnv.cutoff; + + //if (patchCache->waveform==1) reshigh = (reshigh * 3) >> 2; + + depth = patchCache->filtEnv.envdepth; + + //int sensedep = (depth * 127-patchCache->filtEnv.envsense) >> 7; + depth = FIXEDPOINT_UMULT(depth, synth->tables.tvfVelfollowMult[poly->vel][(int)patchCache->filtEnv.envsense], 8); + + int bias = patchCache->tvfbias; + int dist; + + if (bias != 0) { + //FIXME:KG: Is this really based on pitch (as now), or key pressed? + //synth->printDebug("Cutoff before %d", cutoff); + if (patchCache->tvfdir == 0) { + if (noteVal < bias) { + dist = bias - noteVal; + cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8); + } + } else { + // > Bias + if (noteVal > bias) { + dist = noteVal - bias; + cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8); + } + + } + //synth->printDebug("Cutoff after %d", cutoff); + } + + depth = (depth * keyLookup->envDepthMult[patchCache->filtEnv.envdkf]) >> 8; + reshigh = (reshigh * depth) >> 7; + + Bit32s tmp; + + cutoff *= filtVal; + cutoff /= realVal; //FIXME:KG: With filter keyfollow 0, this makes no sense. What's correct? + + reshigh *= filtVal; + reshigh /= realVal; //FIXME:KG: As above for cutoff + + if (patchCache->waveform == 1) { + reshigh = (reshigh * 65) / 100; + } + + if (cutoff > 100) + cutoff = 100; + else if (cutoff < 0) + cutoff = 0; + if (reshigh > 100) + reshigh = 100; + else if (reshigh < 0) + reshigh = 0; + tmp = noteLookup->nfiltTable[cutoff][reshigh]; + //tmp *= keyfollow; + //tmp /= realfollow; + + //synth->printDebug("Cutoff %d, tmp %d, freq %d", cutoff, tmp, tmp * 256); + return tmp; +} + +bool Partial::shouldReverb() { + if (!isActive()) + return false; + return patchCache->reverb; +} + +Bit32u Partial::getAmpEnvelope() { + Bit32s tc; + + EnvelopeStatus *tStat = &envs[EnvelopeType_amp]; + + if (!play) + return 0; + + if (tStat->decaying) { + tc = tStat->envbase; + tc += (tStat->envdist * tStat->envpos) / tStat->envsize; + if (tc < 0) + tc = 0; + if ((tStat->envpos >= tStat->envsize) || (tc == 0)) { + play = false; + // Don't have to worry about prevlevel storage or anything, this partial's about to die + return 0; + } + } else { + if ((tStat->envstat == -1) || (tStat->envpos >= tStat->envsize)) { + if (tStat->envstat == -1) + tStat->envbase = 0; + else + tStat->envbase = patchCache->ampEnv.envlevel[tStat->envstat]; + tStat->envstat++; + tStat->envpos = 0; + if (tStat->envstat == 4) { + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + tc = patchCache->ampEnv.envlevel[3]; + if (!poly->sustain) + startDecay(EnvelopeType_amp, tc); + else + tStat->sustaining = true; + goto PastCalc; + } + Bit8u targetLevel = patchCache->ampEnv.envlevel[tStat->envstat]; + tStat->envdist = targetLevel - tStat->envbase; + Bit32u envTime = patchCache->ampEnv.envtime[tStat->envstat]; + if (targetLevel == 0) { + tStat->envsize = synth->tables.envDecayTime[envTime]; + } else { + int envLevelDelta = abs(tStat->envdist); + if (envTime > synth->tables.envDeltaMaxTime[envLevelDelta]) { + envTime = synth->tables.envDeltaMaxTime[envLevelDelta]; + } + tStat->envsize = synth->tables.envTime[envTime]; + } + + // Time keyfollow is used by all sections of the envelope (confirmed on CM-32L) + tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8); + + switch (tStat->envstat) { + case 0: + //Spot for velocity time follow + //Only used for first attack + tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, synth->tables.envTimeVelfollowMult[(int)patchCache->ampEnv.envvkf][poly->vel], 8); + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + case 1: + case 2: + case 3: + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + default: + synth->printDebug("Invalid TVA envelope number %d hit!", tStat->envstat); + break; + } + + tStat->envsize++; + + if (tStat->envdist != 0) { + tStat->counter = abs(tStat->envsize / tStat->envdist); + //synth->printDebug("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist); + } else { + tStat->counter = 0; + //synth->printDebug("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist); + } + } + tc = tStat->envbase; + tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + tStat->count = tStat->counter; +PastCalc: + tc = (tc * (Bit32s)patchCache->ampEnv.level) / 100; + } + + // Prevlevel storage is bottle neck + tStat->prevlevel = tc; + + //Bias level crap stuff now + + for (int i = 0; i < 2; i++) { + if (patchCache->ampblevel[i]!=0) { + int bias = patchCache->ampbias[i]; + if (patchCache->ampdir[i]==0) { + // < Bias + if (noteVal < bias) { + int dist = bias - noteVal; + tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8); + } + } else { + // > Bias + if (noteVal > bias) { + int dist = noteVal - bias; + tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8); + } + } + } + } + if (tc < 0) { + synth->printDebug("*** ERROR: tc < 0 (%d) at getAmpEnvelope()", tc); + tc = 0; + } + return (Bit32u)tc; +} + +Bit32s Partial::getPitchEnvelope() { + EnvelopeStatus *tStat = &envs[EnvelopeType_pitch]; + + Bit32s tc; + pitchSustain = false; + if (tStat->decaying) { + if (tStat->envpos >= tStat->envsize) + tc = patchCache->pitchEnv.level[4]; + else { + tc = tStat->envbase; + tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + } + } else { + if (tStat->envstat==3) { + tc = patchCache->pitchsustain; + if (poly->sustain) + pitchSustain = true; + else + startDecay(EnvelopeType_pitch, tc); + } else { + if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { + tStat->envstat++; + + tStat->envbase = patchCache->pitchEnv.level[tStat->envstat]; + + Bit32u envTime = patchCache->pitchEnv.time[tStat->envstat]; + int envDiff = abs(patchCache->pitchEnv.level[tStat->envstat] - patchCache->pitchEnv.level[tStat->envstat + 1]); + if (envTime > synth->tables.envDeltaMaxTime[envDiff]) { + envTime = synth->tables.envDeltaMaxTime[envDiff]; + } + + tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow]) >> 8; + + tStat->envpos = 0; + tStat->envsize++; + tStat->envdist = patchCache->pitchEnv.level[tStat->envstat + 1] - tStat->envbase; + } + tc = tStat->envbase; + tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + } + tStat->prevlevel = tc; + } + return tc; +} + +void Partial::startDecayAll() { + startDecay(EnvelopeType_amp, envs[EnvelopeType_amp].prevlevel); + startDecay(EnvelopeType_filt, envs[EnvelopeType_filt].prevlevel); + startDecay(EnvelopeType_pitch, envs[EnvelopeType_pitch].prevlevel); + pitchSustain = false; +} + +void Partial::startDecay(EnvelopeType envnum, Bit32s startval) { + EnvelopeStatus *tStat = &envs[envnum]; + + tStat->sustaining = false; + tStat->decaying = true; + tStat->envpos = 0; + tStat->envbase = startval; + + switch (envnum) { + case EnvelopeType_amp: + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->ampEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8); + tStat->envdist = -startval; + break; + case EnvelopeType_filt: + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->filtEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf], 8); + tStat->envdist = -startval; + break; + case EnvelopeType_pitch: + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->pitchEnv.time[3]], keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow], 8); + tStat->envdist = patchCache->pitchEnv.level[4] - startval; + break; + default: + break; + } + tStat->envsize++; +} diff --git a/audio/softsynth/mt32/partial.h b/audio/softsynth/mt32/partial.h new file mode 100644 index 0000000000..93d8bcd985 --- /dev/null +++ b/audio/softsynth/mt32/partial.h @@ -0,0 +1,148 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_PARTIAL_H +#define MT32EMU_PARTIAL_H + +namespace MT32Emu { + +class Synth; +struct NoteLookup; + +enum EnvelopeType { + EnvelopeType_amp = 0, + EnvelopeType_filt = 1, + EnvelopeType_pitch = 2 +}; + +struct EnvelopeStatus { + Bit32s envpos; + Bit32s envstat; + Bit32s envbase; + Bit32s envdist; + Bit32s envsize; + + bool sustaining; + bool decaying; + Bit32s prevlevel; + + Bit32s counter; + Bit32s count; +}; + +// Class definition of MT-32 partials. 32 in all. +class Partial { +private: + Synth *synth; + + int ownerPart; // -1 if unassigned + int mixType; + int structurePosition; // 0 or 1 of a structure pair + bool useNoisePair; + + Bit16s myBuffer[MAX_SAMPLE_OUTPUT]; + + // Keyfollowed note value +#if MT32EMU_ACCURATENOTES == 1 + NoteLookup noteLookupStorage; + float noteVal; +#else + int noteVal; + int fineShift; +#endif + const NoteLookup *noteLookup; // LUTs for this noteVal + const KeyLookup *keyLookup; // LUTs for the clamped (12..108) key + + // Keyfollowed filter values + int realVal; + int filtVal; + + // Only used for PCM partials + int pcmNum; + PCMWaveEntry *pcmWave; + + int pulsewidth; + + Bit32u lfoPos; + soundaddr partialOff; + + Bit32u ampEnvVal; + Bit32u pitchEnvVal; + + float history[32]; + + bool pitchSustain; + + int loopPos; + + dpoly *poly; + + int bendShift; + + Bit16s *mixBuffers(Bit16s *buf1, Bit16s *buf2, int len); + Bit16s *mixBuffersRingMix(Bit16s *buf1, Bit16s *buf2, int len); + Bit16s *mixBuffersRing(Bit16s *buf1, Bit16s *buf2, int len); + void mixBuffersStereo(Bit16s *buf1, Bit16s *buf2, Bit16s *outBuf, int len); + + Bit32s getFiltEnvelope(); + Bit32u getAmpEnvelope(); + Bit32s getPitchEnvelope(); + + void initKeyFollow(int freqNum); + +public: + const PatchCache *patchCache; + EnvelopeStatus envs[3]; + bool play; + + PatchCache cachebackup; + + Partial *pair; + bool alreadyOutputed; + Bit32u age; + + Partial(Synth *synth); + ~Partial(); + + int getOwnerPart() const; + int getKey() const; + const dpoly *getDpoly() const; + bool isActive(); + void activate(int part); + void deactivate(void); + void startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial); + void startDecay(EnvelopeType envnum, Bit32s startval); + void startDecayAll(); + void setBend(float factor); + bool shouldReverb(); + + // Returns true only if data written to buffer + // This function (unlike the one below it) returns processed stereo samples + // made from combining this single partial with its pair, if it has one. + bool produceOutput(Bit16s * partialBuf, long length); + + // This function produces mono sample output using the partial's private internal buffer + Bit16s *generateSamples(long length); +}; + +} + +#endif diff --git a/audio/softsynth/mt32/partialManager.cpp b/audio/softsynth/mt32/partialManager.cpp new file mode 100644 index 0000000000..3d3b6302db --- /dev/null +++ b/audio/softsynth/mt32/partialManager.cpp @@ -0,0 +1,272 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +#include "mt32emu.h" + +using namespace MT32Emu; + +PartialManager::PartialManager(Synth *useSynth) { + this->synth = useSynth; + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + partialTable[i] = new Partial(synth); +} + +PartialManager::~PartialManager(void) { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + delete partialTable[i]; +} + +void PartialManager::getPerPartPartialUsage(int usage[9]) { + memset(usage, 0, 9 * sizeof (int)); + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->isActive()) + usage[partialTable[i]->getOwnerPart()]++; + } +} + +void PartialManager::clearAlreadyOutputed() { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + partialTable[i]->alreadyOutputed = false; +} + +void PartialManager::ageAll() { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + partialTable[i]->age++; +} + +bool PartialManager::shouldReverb(int i) { + return partialTable[i]->shouldReverb(); +} + +bool PartialManager::produceOutput(int i, Bit16s *buffer, Bit32u bufferLength) { + return partialTable[i]->produceOutput(buffer, bufferLength); +} + +void PartialManager::deactivateAll() { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + partialTable[i]->deactivate(); + } +} + +unsigned int PartialManager::setReserve(Bit8u *rset) { + unsigned int pr = 0; + for (int x = 0; x < 9; x++) { + for (int y = 0; y < rset[x]; y++) { + partialReserveTable[pr] = x; + pr++; + } + } + return pr; +} + +Partial *PartialManager::allocPartial(int partNum) { + Partial *outPartial = NULL; + + // Use the first inactive partial reserved for the specified part (if there are any) + // Otherwise, use the last inactive partial, if any + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (!partialTable[i]->isActive()) { + outPartial = partialTable[i]; + if (partialReserveTable[i] == partNum) + break; + } + } + if (outPartial != NULL) { + outPartial->activate(partNum); + outPartial->age = 0; + } + return outPartial; +} + +unsigned int PartialManager::getFreePartialCount(void) { + int count = 0; + memset(partialPart, 0, sizeof(partialPart)); + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (!partialTable[i]->isActive()) + count++; + else + partialPart[partialTable[i]->getOwnerPart()]++; + } + return count; +} + +/* +bool PartialManager::freePartials(unsigned int needed, int partNum) { + int i; + int myPartPrior = (int)mt32ram.system.reserveSettings[partNum]; + if (myPartPrior 0) { + int selectPart = -1; + //Find the worst offender with more partials than allocated and kill them + most = -1; + mostPart = -1; + int diff; + + for (i=0;i<9;i++) { + diff = partialPart[i] - (int)mt32ram.system.reserveSettings[i]; + + if (diff>0) { + if (diff>most) { + most = diff; + mostPart = i; + } + } + } + selectPart = mostPart; + if (selectPart == -1) { + // All parts are within the allocated limits, you suck + // Look for first partial not of this part that's decaying perhaps? + return false; + } + bool found; + int oldest; + int oldnum; + while (partialPart[selectPart] > (int)mt32ram.system.reserveSettings[selectPart]) { + oldest = -1; + oldnum = -1; + found = false; + for (i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->isActive) { + if (partialTable[i]->ownerPart == selectPart) { + found = true; + if (partialTable[i]->age > oldest) { + oldest = partialTable[i]->age; + oldnum = i; + } + } + } + } + if (!found) break; + partialTable[oldnum]->deactivate(); + --partialPart[selectPart]; + --needed; + } + + } + return true; + + } else { + //This part has reached its max, must kill off its own + bool found; + int oldest; + int oldnum; + while (needed > 0) { + oldest = -1; + oldnum = -1; + found = false; + for (i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->isActive) { + if (partialTable[i]->ownerPart == partNum) { + found = true; + if (partialTable[i]->age > oldest) { + oldest = partialTable[i]->age; + oldnum = i; + } + } + } + } + if (!found) break; + partialTable[oldnum]->deactivate(); + --needed; + } + // Couldn't free enough partials, sorry + if (needed>0) return false; + return true; + } + +} +*/ +bool PartialManager::freePartials(unsigned int needed, int partNum) { + if (needed == 0) { + return true; + } + // Reclaim partials reserved for this part + // Kill those that are already decaying first + /* + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialReserveTable[i] == partNum) { + if (partialTable[i]->ownerPart != partNum) { + if (partialTable[i]->partCache->envs[AMPENV].decaying) { + partialTable[i]->isActive = false; + --needed; + if (needed == 0) + return true; + } + } + } + }*/ + // Then kill those with the lowest part priority -- oldest at the moment + while (needed > 0) { + Bit32u prior = 0; + int priornum = -1; + + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialReserveTable[i] == partNum && partialTable[i]->isActive() && partialTable[i]->getOwnerPart() != partNum) { + /* + if (mt32ram.system.reserveSettings[partialTable[i]->ownerPart] < prior) { + prior = mt32ram.system.reserveSettings[partialTable[i]->ownerPart]; + priornum = i; + }*/ + if (partialTable[i]->age >= prior) { + prior = partialTable[i]->age; + priornum = i; + } + } + } + if (priornum != -1) { + partialTable[priornum]->deactivate(); + --needed; + } else { + break; + } + } + + // Kill off the oldest partials within this part + while (needed > 0) { + Bit32u oldest = 0; + int oldlist = -1; + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->getOwnerPart() == partNum && partialTable[i]->isActive()) { + if (partialTable[i]->age >= oldest) { + oldest = partialTable[i]->age; + oldlist = i; + } + } + } + if (oldlist != -1) { + partialTable[oldlist]->deactivate(); + --needed; + } else { + break; + } + } + return needed == 0; +} + +const Partial *PartialManager::getPartial(unsigned int partialNum) const { + if (partialNum > MT32EMU_MAX_PARTIALS - 1) + return NULL; + return partialTable[partialNum]; +} diff --git a/audio/softsynth/mt32/partialManager.h b/audio/softsynth/mt32/partialManager.h new file mode 100644 index 0000000000..b10f93ff02 --- /dev/null +++ b/audio/softsynth/mt32/partialManager.h @@ -0,0 +1,56 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_PARTIALMANAGER_H +#define MT32EMU_PARTIALMANAGER_H + +namespace MT32Emu { + +class Synth; + +class PartialManager { +private: + Synth *synth; // Only used for sending debug output + + Partial *partialTable[MT32EMU_MAX_PARTIALS]; + Bit32s partialReserveTable[MT32EMU_MAX_PARTIALS]; + Bit32s partialPart[9]; // The count of partials played per part + +public: + + PartialManager(Synth *synth); + ~PartialManager(); + Partial *allocPartial(int partNum); + unsigned int getFreePartialCount(void); + bool freePartials(unsigned int needed, int partNum); + unsigned int setReserve(Bit8u *rset); + void deactivateAll(); + void ageAll(); + bool produceOutput(int i, Bit16s *buffer, Bit32u bufferLength); + bool shouldReverb(int i); + void clearAlreadyOutputed(); + void getPerPartPartialUsage(int usage[9]); + const Partial *getPartial(unsigned int partialNum) const; +}; + +} + +#endif diff --git a/audio/softsynth/mt32/structures.h b/audio/softsynth/mt32/structures.h new file mode 100644 index 0000000000..ef58c1d20f --- /dev/null +++ b/audio/softsynth/mt32/structures.h @@ -0,0 +1,284 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_STRUCTURES_H +#define MT32EMU_STRUCTURES_H + +namespace MT32Emu { + +const unsigned int MAX_SAMPLE_OUTPUT = 4096; + +// MT32EMU_MEMADDR() converts from sysex-padded, MT32EMU_SYSEXMEMADDR converts to it +// Roland provides documentation using the sysex-padded addresses, so we tend to use that in code and output +#define MT32EMU_MEMADDR(x) ((((x) & 0x7f0000) >> 2) | (((x) & 0x7f00) >> 1) | ((x) & 0x7f)) +#define MT32EMU_SYSEXMEMADDR(x) ((((x) & 0x1FC000) << 2) | (((x) & 0x3F80) << 1) | ((x) & 0x7f)) + +#ifdef _MSC_VER +#define MT32EMU_ALIGN_PACKED __declspec(align(1)) +typedef unsigned __int64 Bit64u; +typedef signed __int64 Bit64s; +#else +#define MT32EMU_ALIGN_PACKED __attribute__((packed)) +typedef unsigned long long Bit64u; +typedef signed long long Bit64s; +#endif + +typedef unsigned int Bit32u; +typedef signed int Bit32s; +typedef unsigned short int Bit16u; +typedef signed short int Bit16s; +typedef unsigned char Bit8u; +typedef signed char Bit8s; + +// The following structures represent the MT-32's memory +// Since sysex allows this memory to be written to in blocks of bytes, +// we keep this packed so that we can copy data into the various +// banks directly +#if defined(_MSC_VER) || defined (__MINGW32__) +#pragma pack(push, 1) +#else +#pragma pack(1) +#endif + +struct TimbreParam { + struct commonParam { + char name[10]; + Bit8u pstruct12; // 1&2 0-12 (1-13) + Bit8u pstruct34; // #3&4 0-12 (1-13) + Bit8u pmute; // 0-15 (0000-1111) + Bit8u nosustain; // 0-1(Normal, No sustain) + } MT32EMU_ALIGN_PACKED common; + + struct partialParam { + struct wgParam { + Bit8u coarse; // 0-96 (C1,C#1-C9) + Bit8u fine; // 0-100 (-50 to +50 (cents?)) + Bit8u keyfollow; // 0-16 (-1,-1/2,0,1,1/8,1/4,3/8,1/2,5/8,3/4,7/8,1,5/4,3/2,2.s1,s2) + Bit8u bender; // 0,1 (ON/OFF) + Bit8u waveform; // MT-32: 0-1 (SQU/SAW); LAPC-I: WG WAVEFORM/PCM BANK 0 - 3 (SQU/1, SAW/1, SQU/2, SAW/2) + Bit8u pcmwave; // 0-127 (1-128) + Bit8u pulsewid; // 0-100 + Bit8u pwvelo; // 0-14 (-7 - +7) + } MT32EMU_ALIGN_PACKED wg; + + struct envParam { + Bit8u depth; // 0-10 + Bit8u sensitivity; // 1-100 + Bit8u timekeyfollow; // 0-4 + Bit8u time[4]; // 1-100 + Bit8u level[5]; // 1-100 (-50 - +50) + } MT32EMU_ALIGN_PACKED env; + + struct lfoParam { + Bit8u rate; // 0-100 + Bit8u depth; // 0-100 + Bit8u modsense; // 0-100 + } MT32EMU_ALIGN_PACKED lfo; + + struct tvfParam { + Bit8u cutoff; // 0-100 + Bit8u resonance; // 0-30 + Bit8u keyfollow; // 0-16 (-1,-1/2,1/4,0,1,1/8,1/4,3/8,1/2,5/8,3/2,7/8,1,5/4,3/2,2,s1,s2) + Bit8u biaspoint; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel; // 0-14 (-7 - +7) + Bit8u envdepth; // 0-100 + Bit8u envsense; // 0-100 + Bit8u envdkf; // DEPTH KEY FOLL0W 0-4 + Bit8u envtkf; // TIME KEY FOLLOW 0-4 + Bit8u envtime[5]; // 1-100 + Bit8u envlevel[4]; // 1-100 + } MT32EMU_ALIGN_PACKED tvf; + + struct tvaParam { + Bit8u level; // 0-100 + Bit8u velosens; // 0-100 + Bit8u biaspoint1; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel1; // 0-12 (-12 - 0) + Bit8u biaspoint2; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel2; // 0-12 (-12 - 0) + Bit8u envtkf; // TIME KEY FOLLOW 0-4 + Bit8u envvkf; // VELOS KEY FOLL0W 0-4 + Bit8u envtime[5]; // 1-100 + Bit8u envlevel[4]; // 1-100 + } MT32EMU_ALIGN_PACKED tva; + } MT32EMU_ALIGN_PACKED partial[4]; +} MT32EMU_ALIGN_PACKED; + +struct PatchParam { + Bit8u timbreGroup; // TIMBRE GROUP 0-3 (group A, group B, Memory, Rhythm) + Bit8u timbreNum; // TIMBRE NUMBER 0-63 + Bit8u keyShift; // KEY SHIFT 0-48 (-24 - +24 semitones) + Bit8u fineTune; // FINE TUNE 0-100 (-50 - +50 cents) + Bit8u benderRange; // BENDER RANGE 0-24 + Bit8u assignMode; // ASSIGN MODE 0-3 (POLY1, POLY2, POLY3, POLY4) + Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) + Bit8u dummy; // (DUMMY) +} MT32EMU_ALIGN_PACKED; + +struct MemParams { + // NOTE: The MT-32 documentation only specifies PatchTemp areas for parts 1-8. + // The LAPC-I documentation specified an additional area for rhythm at the end, + // where all parameters but fine tune, assign mode and output level are ignored + struct PatchTemp { + PatchParam patch; + Bit8u outlevel; // OUTPUT LEVEL 0-100 + Bit8u panpot; // PANPOT 0-14 (R-L) + Bit8u dummyv[6]; + } MT32EMU_ALIGN_PACKED; + + PatchTemp patchSettings[9]; + + struct RhythmTemp { + Bit8u timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF) + Bit8u outlevel; // OUTPUT LEVEL 0-100 + Bit8u panpot; // PANPOT 0-14 (R-L) + Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) + } MT32EMU_ALIGN_PACKED; + + RhythmTemp rhythmSettings[85]; + + TimbreParam timbreSettings[8]; + + PatchParam patches[128]; + + // NOTE: There are only 30 timbres in the "rhythm" bank for MT-32; the additional 34 are for LAPC-I and above + struct PaddedTimbre { + TimbreParam timbre; + Bit8u padding[10]; + } MT32EMU_ALIGN_PACKED; + + PaddedTimbre timbres[64 + 64 + 64 + 64]; // Group A, Group B, Memory, Rhythm + + struct SystemArea { + Bit8u masterTune; // MASTER TUNE 0-127 432.1-457.6Hz + Bit8u reverbMode; // REVERB MODE 0-3 (room, hall, plate, tap delay) + Bit8u reverbTime; // REVERB TIME 0-7 (1-8) + Bit8u reverbLevel; // REVERB LEVEL 0-7 (1-8) + Bit8u reserveSettings[9]; // PARTIAL RESERVE (PART 1) 0-32 + Bit8u chanAssign[9]; // MIDI CHANNEL (PART1) 0-16 (1-16,OFF) + Bit8u masterVol; // MASTER VOLUME 0-100 + } MT32EMU_ALIGN_PACKED; + + SystemArea system; +}; + +#if defined(_MSC_VER) || defined (__MINGW32__) +#pragma pack(pop) +#else +#pragma pack() +#endif + +struct PCMWaveEntry { + Bit32u addr; + Bit32u len; + double tune; + bool loop; +}; + +struct soundaddr { + Bit16u pcmplace; + Bit16u pcmoffset; +}; + +struct StereoVolume { + Bit16s leftvol; + Bit16s rightvol; +}; + +// This is basically a per-partial, pre-processed combination of timbre and patch/rhythm settings +struct PatchCache { + bool playPartial; + bool PCMPartial; + int pcm; + char waveform; + int pulsewidth; + int pwsens; + + float pitch; + + int lfodepth; + int lforate; + Bit32u lfoperiod; + int modsense; + + float pitchKeyfollow; + + int filtkeyfollow; + + int tvfbias; + int tvfblevel; + int tvfdir; + + int ampbias[2]; + int ampblevel[2]; + int ampdir[2]; + + int ampdepth; + int amplevel; + + bool useBender; + float benderRange; // 0.0, 1.0, .., 24.0 (semitones) + + TimbreParam::partialParam::envParam pitchEnv; + TimbreParam::partialParam::tvaParam ampEnv; + TimbreParam::partialParam::tvfParam filtEnv; + + Bit32s pitchsustain; + Bit32s filtsustain; + + Bit32u structureMix; + int structurePosition; + int structurePair; + + // The following fields are actually common to all partials in the timbre + bool dirty; + Bit32u partialCount; + bool sustain; + float pitchShift; + bool reverb; + const StereoVolume *pansetptr; +}; + +class Partial; // Forward reference for class defined in partial.h + +struct dpoly { + bool isPlaying; + + unsigned int key; + int freqnum; + int vel; + + bool isDecay; + + const Bit32u *volumeptr; + + Partial *partials[4]; + + bool pedalhold; // This marks keys that have been released on the keyboard, but are being held by the pedal + bool sustain; + + bool isActive() const; + Bit32u getAge() const; +}; + +} + +#endif diff --git a/audio/softsynth/mt32/synth.cpp b/audio/softsynth/mt32/synth.cpp new file mode 100644 index 0000000000..16460795a5 --- /dev/null +++ b/audio/softsynth/mt32/synth.cpp @@ -0,0 +1,1198 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "mt32emu.h" + +#include "common/str.h" + +#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__) +// Older versions of Mac OS X didn't supply a powf function, so using it +// will cause a binary incompatibility when trying to run a binary built +// on a newer OS X release on an olderr one. And Solaris 8 doesn't provide +// powf, floorf, fabsf etc. at all. +// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in +// libstdc++. math/stubs.o should be empty, but it comes with a symbol for +// powf, resulting in a linker error because of multiple definitions. +// Hence we re-define them here. The only potential drawback is that it +// might be a little bit slower this way. +#define powf(x,y) ((float)pow(x,y)) +#define floorf(x) ((float)floor(x)) +#define fabsf(x) ((float)fabs(x)) +#endif + +namespace MT32Emu { + +const int MAX_SYSEX_SIZE = 512; + +const ControlROMMap ControlROMMaps[5] = { + // ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrB tmbrBO, tmbrR trC rhythm rhyC rsrv panpot prog + {0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57D0, 0x57E2}, // MT-32 revision 0 + {0x4014, 22, "\000 ver1.06 31 Aug, 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x7414, 85, 0x57D9, 0x57E2, 0x57F4}, // MT-32 revision 0 + {0x4010, 22, "\000 ver1.07 10 Oct, 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57BA, 0x57CC}, // MT-32 revision 1 + {0x4010, 22, "\000verX.XX 30 Sep, 88 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x741C, 85, 0x57E5, 0x57EE, 0x5800}, // MT-32 Blue Ridge mod + {0x2205, 22, "\000CM32/LAPC1.02 891205", 0x8100, 256, 0x8000, 0x8000, 0x8080, 0x8000, 0x8500, 64, 0x8580, 85, 0x4F93, 0x4F9C, 0x4FAE} // CM-32L + // (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp) +}; + +float iir_filter_normal(float input, float *hist1_ptr, float *coef_ptr) { + float *hist2_ptr; + float output,new_hist; + + hist2_ptr = hist1_ptr + 1; // next history + + // 1st number of coefficients array is overall input scale factor, or filter gain + output = input * (*coef_ptr++); + + output = output - *hist1_ptr * (*coef_ptr++); + new_hist = output - *hist2_ptr * (*coef_ptr++); // poles + + output = new_hist + *hist1_ptr * (*coef_ptr++); + output = output + *hist2_ptr * (*coef_ptr++); // zeros + + *hist2_ptr++ = *hist1_ptr; + *hist1_ptr++ = new_hist; + hist1_ptr++; + hist2_ptr++; + + // i = 1 + output = output - *hist1_ptr * (*coef_ptr++); + new_hist = output - *hist2_ptr * (*coef_ptr++); // poles + + output = new_hist + *hist1_ptr * (*coef_ptr++); + output = output + *hist2_ptr * (*coef_ptr++); // zeros + + *hist2_ptr++ = *hist1_ptr; + *hist1_ptr++ = new_hist; + + return(output); +} + +Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) { + for (unsigned int i = 0; i < len; i++) { + checksum = checksum + data[i]; + } + checksum = checksum & 0x7f; + if (checksum) + checksum = 0x80 - checksum; + return checksum; +} + +Synth::Synth() { + isOpen = false; + reverbModel = NULL; + partialManager = NULL; + memset(parts, 0, sizeof(parts)); +} + +Synth::~Synth() { + close(); // Make sure we're closed and everything is freed +} + +int Synth::report(ReportType type, const void *data) { + if (myProp.report != NULL) { + return myProp.report(myProp.userData, type, data); + } + return 0; +} + +void Synth::printDebug(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + if (myProp.printDebug != NULL) { + myProp.printDebug(myProp.userData, fmt, ap); + } else { + vprintf(fmt, ap); + printf("\n"); + } + va_end(ap); +} + +void Synth::initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel) { + // FIXME:KG: I don't think it's necessary to recreate the reverbModel... Just set the parameters + delete reverbModel; + reverbModel = new revmodel(); + + switch (newRevMode) { + case 0: + reverbModel->setroomsize(.1f); + reverbModel->setdamp(.75f); + break; + case 1: + reverbModel->setroomsize(.5f); + reverbModel->setdamp(.5f); + break; + case 2: + reverbModel->setroomsize(.5f); + reverbModel->setdamp(.1f); + break; + case 3: + reverbModel->setroomsize(1.0f); + reverbModel->setdamp(.75f); + break; + default: + reverbModel->setroomsize(.1f); + reverbModel->setdamp(.5f); + break; + } + reverbModel->setdry(1); + reverbModel->setwet((float)newRevLevel / 8.0f); + reverbModel->setwidth((float)newRevTime / 8.0f); +} + +File *Synth::openFile(const char *filename, File::OpenMode mode) { + // It should never happen that openFile is NULL in our use case. + // Just to cover the case where something is horrible wrong we + // use an assert here. + assert(myProp.openFile != NULL); + return myProp.openFile(myProp.userData, filename, mode); +} + +void Synth::closeFile(File *file) { + if (myProp.closeFile != NULL) { + myProp.closeFile(myProp.userData, file); + } else { + file->close(); + delete file; + } +} + +bool Synth::loadPreset(File *file) { + bool inSys = false; + Bit8u sysexBuf[MAX_SYSEX_SIZE]; + Bit16u syslen = 0; + bool rc = true; + for (;;) { + Bit8u c; + if (!file->readBit8u(&c)) { + if (!file->isEOF()) { + rc = false; + } + break; + } + sysexBuf[syslen] = c; + if (inSys) { + syslen++; + if (c == 0xF7) { + playSysex(&sysexBuf[0], syslen); + inSys = false; + syslen = 0; + } else if (syslen == MAX_SYSEX_SIZE) { + printDebug("MAX_SYSEX_SIZE (%d) exceeded while processing preset, ignoring message", MAX_SYSEX_SIZE); + inSys = false; + syslen = 0; + } + } else if (c == 0xF0) { + syslen++; + inSys = true; + } + } + return rc; +} + +bool Synth::loadControlROM(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); // ROM File + if (file == NULL) { + return false; + } + bool rc = (file->read(controlROMData, CONTROL_ROM_SIZE) == CONTROL_ROM_SIZE); + + closeFile(file); + if (!rc) + return rc; + + // Control ROM successfully loaded, now check whether it's a known type + controlROMMap = NULL; + for (unsigned int i = 0; i < sizeof (ControlROMMaps) / sizeof (ControlROMMaps[0]); i++) { + if (memcmp(&controlROMData[ControlROMMaps[i].idPos], ControlROMMaps[i].idBytes, ControlROMMaps[i].idLen) == 0) { + controlROMMap = &ControlROMMaps[i]; + return true; + } + } + return false; +} + +bool Synth::loadPCMROM(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); // ROM File + if (file == NULL) { + return false; + } + bool rc = true; + int i; + for (i = 0; i < pcmROMSize; i++) { + Bit8u s; + if (!file->readBit8u(&s)) { + if (!file->isEOF()) { + rc = false; + } + break; + } + Bit8u c; + if (!file->readBit8u(&c)) { + if (!file->isEOF()) { + rc = false; + } else { + printDebug("PCM ROM file has an odd number of bytes! Ignoring last"); + } + break; + } + + short e; + int bit; + int u; + int order[16] = {0, 9, 1 ,2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8}; + + e = 0; + for (u = 0; u < 15; u++) { + if (order[u] < 8) + bit = (s >> (7 - order[u])) & 0x1; + else + bit = (c >> (7 - (order[u] - 8))) & 0x1; + e = e | (short)(bit << (15 - u)); + } + + /* + //Bit16s e = ( ((s & 0x7f) << 4) | ((c & 0x40) << 6) | ((s & 0x80) << 6) | ((c & 0x3f))) << 2; + if (e<0) + e = -32767 - e; + int ut = abs(e); + int dif = 0x7fff - ut; + x = exp(((float)((float)0x8000-(float)dif) / (float)0x1000)); + e = (int)((float)e * (x/3200)); + */ + + // File is companded (dB?), convert to linear PCM + // MINDB = -96 + // MAXDB = -15 + float testval; + testval = (float)((~e) & 0x7fff); + testval = -(testval / 400.00f); + //testval = -(testval / 341.32291666666666666666666666667); + float vol = powf(8, testval / 20) * 32767.0f; + + if (e > 0) + vol = -vol; + + pcmROMData[i] = (Bit16s)vol; + } + if (i != pcmROMSize) { + printDebug("PCM ROM file is too short (expected %d, got %d)", pcmROMSize, i); + rc = false; + } + closeFile(file); + return rc; +} + +bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) { + ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress]; + for (int i = 0; i < count; i++) { + int rAddr = tps[i].pos * 0x800; + int rLenExp = (tps[i].len & 0x70) >> 4; + int rLen = 0x800 << rLenExp; + bool rLoop = (tps[i].len & 0x80) != 0; + //Bit8u rFlag = tps[i].len & 0x0F; + Bit16u rTuneOffset = (tps[i].pitchMSB << 8) | tps[i].pitchLSB; + // The number below is confirmed to a reasonable degree of accuracy on CM-32L + double STANDARDFREQ = 442.0; + float rTune = (float)(STANDARDFREQ * pow(2.0, (0x5000 - rTuneOffset) / 4056.0 - 9.0 / 12.0)); + //printDebug("%f,%d,%d", (double)pTune, tps[i].pitchCoarse, tps[i].pitchFine); + if (rAddr + rLen > pcmROMSize) { + printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen); + return false; + } + pcmWaves[i].addr = rAddr; + pcmWaves[i].len = rLen; + pcmWaves[i].loop = rLoop; + pcmWaves[i].tune = rTune; + } + return false; +} + +bool Synth::initRhythmTimbre(int timbreNum, const Bit8u *mem, unsigned int memLen) { + if (memLen < sizeof(TimbreParam::commonParam)) { + return false; + } + TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; + memcpy(&timbre->common, mem, 14); + unsigned int memPos = 14; + char drumname[11]; + memset(drumname, 0, 11); + memcpy(drumname, timbre->common.name, 10); + for (int t = 0; t < 4; t++) { + if (((timbre->common.pmute >> t) & 0x1) == 0x1) { + if (memPos + 58 >= memLen) { + return false; + } + memcpy(&timbre->partial[t], mem + memPos, 58); + memPos += 58; + } + } + return true; +} + +bool Synth::initRhythmTimbres(Bit16u mapAddress, Bit16u count) { + const Bit8u *drumMap = &controlROMData[mapAddress]; + int timbreNum = 192; + for (Bit16u i = 0; i < count * 2; i += 2) { + Bit16u address = (drumMap[i + 1] << 8) | drumMap[i]; + /* + // This check is nonsensical when the control ROM is the full 64KB addressable by 16-bit absolute pointers (which it is) + if (address >= CONTROL_ROM_SIZE) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address); + return false; + } + */ + if (!initRhythmTimbre(timbreNum++, &controlROMData[address], CONTROL_ROM_SIZE - address)) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre 0x%04x", i, address); + return false; + } + } + return true; +} + +bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre) { + for (Bit16u i = mapAddress; i < mapAddress + 0x80; i += 2) { + Bit16u address = (controlROMData[i + 1] << 8) | controlROMData[i]; + if (address + sizeof(TimbreParam) > CONTROL_ROM_SIZE) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address); + return false; + } + address = address + offset; + TimbreParam *timbre = &mt32ram.timbres[startTimbre++].timbre; + memcpy(timbre, &controlROMData[address], sizeof(TimbreParam)); + } + return true; +} + +bool Synth::open(SynthProperties &useProp) { + if (isOpen) + return false; + + myProp = useProp; + if (useProp.baseDir != NULL) { + myProp.baseDir = new char[strlen(useProp.baseDir) + 1]; + strcpy(myProp.baseDir, useProp.baseDir); + } + + // This is to help detect bugs + memset(&mt32ram, '?', sizeof(mt32ram)); + + printDebug("Loading Control ROM"); + if (!loadControlROM("CM32L_CONTROL.ROM")) { + if (!loadControlROM("MT32_CONTROL.ROM")) { + printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM"); + report(ReportType_errorControlROM, NULL); + return false; + } + } + + // 512KB PCM ROM for MT-32, etc. + // 1MB PCM ROM for CM-32L, LAPC-I, CM-64, CM-500 + // Note that the size below is given in samples (16-bit), not bytes + pcmROMSize = controlROMMap->pcmCount == 256 ? 512 * 1024 : 256 * 1024; + pcmROMData = new Bit16s[pcmROMSize]; + + printDebug("Loading PCM ROM"); + if (!loadPCMROM("CM32L_PCM.ROM")) { + if (!loadPCMROM("MT32_PCM.ROM")) { + printDebug("Init Error - Missing MT32_PCM.ROM"); + report(ReportType_errorPCMROM, NULL); + return false; + } + } + + printDebug("Initialising Timbre Bank A"); + if (!initTimbres(controlROMMap->timbreAMap, controlROMMap->timbreAOffset, 0)) { + return false; + } + + printDebug("Initialising Timbre Bank B"); + if (!initTimbres(controlROMMap->timbreBMap, controlROMMap->timbreBOffset, 64)) { + return false; + } + + printDebug("Initialising Timbre Bank R"); + if (!initRhythmTimbres(controlROMMap->timbreRMap, controlROMMap->timbreRCount)) { + return false; + } + + printDebug("Initialising Timbre Bank M"); + // CM-64 seems to initialise all bytes in this bank to 0. + memset(&mt32ram.timbres[128], 0, sizeof (mt32ram.timbres[128]) * 64); + + partialManager = new PartialManager(this); + + pcmWaves = new PCMWaveEntry[controlROMMap->pcmCount]; + + printDebug("Initialising PCM List"); + initPCMList(controlROMMap->pcmTable, controlROMMap->pcmCount); + + printDebug("Initialising Rhythm Temp"); + memcpy(mt32ram.rhythmSettings, &controlROMData[controlROMMap->rhythmSettings], controlROMMap->rhythmSettingsCount * 4); + + printDebug("Initialising Patches"); + for (Bit8u i = 0; i < 128; i++) { + PatchParam *patch = &mt32ram.patches[i]; + patch->timbreGroup = i / 64; + patch->timbreNum = i % 64; + patch->keyShift = 24; + patch->fineTune = 50; + patch->benderRange = 12; + patch->assignMode = 0; + patch->reverbSwitch = 1; + patch->dummy = 0; + } + + printDebug("Initialising System"); + // The MT-32 manual claims that "Standard pitch" is 442Hz. + mt32ram.system.masterTune = 0x40; // Confirmed on CM-64 as 0x4A, but SCUMM games use 0x40 and we don't want to initialise twice + mt32ram.system.reverbMode = 0; // Confirmed + mt32ram.system.reverbTime = 5; // Confirmed + mt32ram.system.reverbLevel = 3; // Confirmed + memcpy(mt32ram.system.reserveSettings, &controlROMData[controlROMMap->reserveSettings], 9); // Confirmed + for (Bit8u i = 0; i < 9; i++) { + // This is the default: {1, 2, 3, 4, 5, 6, 7, 8, 9} + // An alternative configuration can be selected by holding "Master Volume" + // and pressing "PART button 1" on the real MT-32's frontpanel. + // The channel assignment is then {0, 1, 2, 3, 4, 5, 6, 7, 9} + mt32ram.system.chanAssign[i] = i + 1; + } + mt32ram.system.masterVol = 100; // Confirmed + if (!refreshSystem()) + return false; + + for (int i = 0; i < 8; i++) { + mt32ram.patchSettings[i].outlevel = 80; + mt32ram.patchSettings[i].panpot = controlROMData[controlROMMap->panSettings + i]; + memset(mt32ram.patchSettings[i].dummyv, 0, sizeof(mt32ram.patchSettings[i].dummyv)); + parts[i] = new Part(this, i); + parts[i]->setProgram(controlROMData[controlROMMap->programSettings + i]); + } + parts[8] = new RhythmPart(this, 8); + + // For resetting mt32 mid-execution + mt32default = mt32ram; + + iirFilter = &iir_filter_normal; + +#ifdef MT32EMU_HAVE_X86 + bool availableSSE = DetectSIMD(); + bool available3DNow = Detect3DNow(); + + if (availableSSE) + report(ReportType_availableSSE, NULL); + if (available3DNow) + report(ReportType_available3DNow, NULL); + + if (available3DNow) { + printDebug("Detected and using SIMD (AMD 3DNow) extensions"); + iirFilter = &iir_filter_3dnow; + report(ReportType_using3DNow, NULL); + } else if (availableSSE) { + printDebug("Detected and using SIMD (Intel SSE) extensions"); + iirFilter = &iir_filter_sse; + report(ReportType_usingSSE, NULL); + } +#endif + + isOpen = true; + isEnabled = false; + + printDebug("*** Initialisation complete ***"); + return true; +} + +void Synth::close(void) { + if (!isOpen) + return; + + tables.freeNotes(); + if (partialManager != NULL) { + delete partialManager; + partialManager = NULL; + } + + if (reverbModel != NULL) { + delete reverbModel; + reverbModel = NULL; + } + + for (int i = 0; i < 9; i++) { + if (parts[i] != NULL) { + delete parts[i]; + parts[i] = NULL; + } + } + if (myProp.baseDir != NULL) { + delete myProp.baseDir; + myProp.baseDir = NULL; + } + + delete[] pcmWaves; + delete[] pcmROMData; + isOpen = false; +} + +void Synth::playMsg(Bit32u msg) { + // FIXME: Implement active sensing + unsigned char code = (unsigned char)((msg & 0x0000F0) >> 4); + unsigned char chan = (unsigned char) (msg & 0x00000F); + unsigned char note = (unsigned char)((msg & 0x00FF00) >> 8); + unsigned char velocity = (unsigned char)((msg & 0xFF0000) >> 16); + isEnabled = true; + + //printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note); + + signed char part = chantable[chan]; + if (part < 0 || part > 8) { + printDebug("Play msg on unreg chan %d (%d): code=0x%01x, vel=%d", chan, part, code, velocity); + return; + } + playMsgOnPart(part, code, note, velocity); +} + +void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity) { + Bit32u bend; + + //printDebug("Synth::playMsg(0x%02x)",msg); + switch (code) { + case 0x8: + //printDebug("Note OFF - Part %d", part); + // The MT-32 ignores velocity for note off + parts[part]->stopNote(note); + break; + case 0x9: + //printDebug("Note ON - Part %d, Note %d Vel %d", part, note, velocity); + if (velocity == 0) { + // MIDI defines note-on with velocity 0 as being the same as note-off with velocity 40 + parts[part]->stopNote(note); + } else { + parts[part]->playNote(note, velocity); + } + break; + case 0xB: // Control change + switch (note) { + case 0x01: // Modulation + //printDebug("Modulation: %d", velocity); + parts[part]->setModulation(velocity); + break; + case 0x07: // Set volume + //printDebug("Volume set: %d", velocity); + parts[part]->setVolume(velocity); + break; + case 0x0A: // Pan + //printDebug("Pan set: %d", velocity); + parts[part]->setPan(velocity); + break; + case 0x0B: + //printDebug("Expression set: %d", velocity); + parts[part]->setExpression(velocity); + break; + case 0x40: // Hold (sustain) pedal + //printDebug("Hold pedal set: %d", velocity); + parts[part]->setHoldPedal(velocity>=64); + break; + + case 0x79: // Reset all controllers + //printDebug("Reset all controllers"); + //FIXME: Check for accuracy against real thing + parts[part]->setVolume(100); + parts[part]->setExpression(127); + parts[part]->setPan(64); + parts[part]->setBend(0x2000); + parts[part]->setHoldPedal(false); + break; + + case 0x7B: // All notes off + //printDebug("All notes off"); + parts[part]->allNotesOff(); + break; + + default: + printDebug("Unknown MIDI Control code: 0x%02x - vel 0x%02x", note, velocity); + break; + } + + break; + case 0xC: // Program change + //printDebug("Program change %01x", note); + parts[part]->setProgram(note); + break; + case 0xE: // Pitch bender + bend = (velocity << 7) | (note); + //printDebug("Pitch bender %02x", bend); + parts[part]->setBend(bend); + break; + default: + printDebug("Unknown Midi code: 0x%01x - %02x - %02x", code, note, velocity); + break; + } + + //midiOutShortMsg(m_out, msg); +} + +void Synth::playSysex(const Bit8u *sysex, Bit32u len) { + if (len < 2) { + printDebug("playSysex: Message is too short for sysex (%d bytes)", len); + } + if (sysex[0] != 0xF0) { + printDebug("playSysex: Message lacks start-of-sysex (0xF0)"); + return; + } + // Due to some programs (e.g. Java) sending buffers with junk at the end, we have to go through and find the end marker rather than relying on len. + Bit32u endPos; + for (endPos = 1; endPos < len; endPos++) + { + if (sysex[endPos] == 0xF7) + break; + } + if (endPos == len) { + printDebug("playSysex: Message lacks end-of-sysex (0xf7)"); + return; + } + playSysexWithoutFraming(sysex + 1, endPos - 1); +} + +void Synth::playSysexWithoutFraming(const Bit8u *sysex, Bit32u len) { + if (len < 4) { + printDebug("playSysexWithoutFraming: Message is too short (%d bytes)!", len); + return; + } + if (sysex[0] != SYSEX_MANUFACTURER_ROLAND) { + printDebug("playSysexWithoutFraming: Header not intended for this device manufacturer: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + if (sysex[2] == SYSEX_MDL_D50) { + printDebug("playSysexWithoutFraming: Header is intended for model D-50 (not yet supported): %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + else if (sysex[2] != SYSEX_MDL_MT32) { + printDebug("playSysexWithoutFraming: Header not intended for model MT-32: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + playSysexWithoutHeader(sysex[1], sysex[3], sysex + 4, len - 4); +} + +void Synth::playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len) { + if (device > 0x10) { + // We have device ID 0x10 (default, but changeable, on real MT-32), < 0x10 is for channels + printDebug("playSysexWithoutHeader: Message is not intended for this device ID (provided: %02x, expected: 0x10 or channel)", (int)device); + return; + } + if (len < 4) { + printDebug("playSysexWithoutHeader: Message is too short (%d bytes)!", len); + return; + } + unsigned char checksum = calcSysexChecksum(sysex, len - 1, 0); + if (checksum != sysex[len - 1]) { + printDebug("playSysexWithoutHeader: Message checksum is incorrect (provided: %02x, expected: %02x)!", sysex[len - 1], checksum); + return; + } + len -= 1; // Exclude checksum + switch (command) { + case SYSEX_CMD_DT1: + writeSysex(device, sysex, len); + break; + case SYSEX_CMD_RQ1: + readSysex(device, sysex, len); + break; + default: + printDebug("playSysexWithoutFraming: Unsupported command %02x", command); + return; + } +} + +void Synth::readSysex(unsigned char /*device*/, const Bit8u * /*sysex*/, Bit32u /*len*/) { +} + +const MemoryRegion memoryRegions[8] = { + {MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9}, + {MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85}, + {MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8}, + {MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128}, + {MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64}, + {MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::SystemArea), 1}, + {MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1}, + {MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1} +}; + +const int NUM_REGIONS = sizeof(memoryRegions) / sizeof(MemoryRegion); + +void Synth::writeSysex(unsigned char device, const Bit8u *sysex, Bit32u len) { + Bit32u addr = (sysex[0] << 16) | (sysex[1] << 8) | (sysex[2]); + addr = MT32EMU_MEMADDR(addr); + sysex += 3; + len -= 3; + //printDebug("Sysex addr: 0x%06x", MT32EMU_SYSEXMEMADDR(addr)); + // NOTE: Please keep both lower and upper bounds in each check, for ease of reading + + // Process channel-specific sysex by converting it to device-global + if (device < 0x10) { + printDebug("WRITE-CHANNEL: Channel %d temp area 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr)); + if (/*addr >= MT32EMU_MEMADDR(0x000000) && */addr < MT32EMU_MEMADDR(0x010000)) { + int offset; + if (chantable[device] == -1) { + printDebug(" (Channel not mapped to a partial... 0 offset)"); + offset = 0; + } else if (chantable[device] == 8) { + printDebug(" (Channel mapped to rhythm... 0 offset)"); + offset = 0; + } else { + offset = chantable[device] * sizeof(MemParams::PatchTemp); + printDebug(" (Setting extra offset to %d)", offset); + } + addr += MT32EMU_MEMADDR(0x030000) + offset; + } else if (/*addr >= 0x010000 && */ addr < MT32EMU_MEMADDR(0x020000)) { + addr += MT32EMU_MEMADDR(0x030110) - MT32EMU_MEMADDR(0x010000); + } else if (/*addr >= 0x020000 && */ addr < MT32EMU_MEMADDR(0x030000)) { + int offset; + if (chantable[device] == -1) { + printDebug(" (Channel not mapped to a partial... 0 offset)"); + offset = 0; + } else if (chantable[device] == 8) { + printDebug(" (Channel mapped to rhythm... 0 offset)"); + offset = 0; + } else { + offset = chantable[device] * sizeof(TimbreParam); + printDebug(" (Setting extra offset to %d)", offset); + } + addr += MT32EMU_MEMADDR(0x040000) - MT32EMU_MEMADDR(0x020000) + offset; + } else { + printDebug("PlaySysexWithoutHeader: Invalid channel %d address 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr)); + return; + } + } + + // Process device-global sysex (possibly converted from channel-specific sysex above) + for (;;) { + // Find the appropriate memory region + int regionNum; + const MemoryRegion *region = NULL; // Initialised to please compiler + for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) { + region = &memoryRegions[regionNum]; + if (region->contains(addr)) { + writeMemoryRegion(region, addr, region->getClampedLen(addr, len), sysex); + break; + } + } + if (regionNum == NUM_REGIONS) { + printDebug("Sysex write to unrecognised address %06x, len %d", MT32EMU_SYSEXMEMADDR(addr), len); + break; + } + Bit32u next = region->next(addr, len); + if (next == 0) { + break; + } + addr += next; + sysex += next; + len -= next; + } +} + +void Synth::readMemory(Bit32u addr, Bit32u len, Bit8u *data) { + int regionNum; + const MemoryRegion *region = NULL; + for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) { + region = &memoryRegions[regionNum]; + if (region->contains(addr)) { + readMemoryRegion(region, addr, len, data); + break; + } + } +} + +void Synth::readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data) { + unsigned int first = region->firstTouched(addr); + //unsigned int last = region->lastTouched(addr, len); + unsigned int off = region->firstTouchedOffset(addr); + len = region->getClampedLen(addr, len); + + unsigned int m; + + switch (region->type) { + case MR_PatchTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.patchSettings[first])[off + m]; + break; + case MR_RhythmTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m]; + break; + case MR_TimbreTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.timbreSettings[first])[off + m]; + break; + case MR_Patches: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.patches[first])[off + m]; + break; + case MR_Timbres: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.timbres[first])[off + m]; + break; + case MR_System: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.system)[m + off]; + break; + default: + for (m = 0; m < len; m += 2) { + data[m] = 0xff; + if (m + 1 < len) { + data[m+1] = (Bit8u)region->type; + } + } + // TODO: Don't care about the others ATM + break; + } + +} + +void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data) { + unsigned int first = region->firstTouched(addr); + unsigned int last = region->lastTouched(addr, len); + unsigned int off = region->firstTouchedOffset(addr); + switch (region->type) { + case MR_PatchTemp: + for (unsigned int m = 0; m < len; m++) { + ((Bit8u *)&mt32ram.patchSettings[first])[off + m] = data[m]; + } + //printDebug("Patch temp: Patch %d, offset %x, len %d", off/16, off % 16, len); + + for (unsigned int i = first; i <= last; i++) { + int absTimbreNum = mt32ram.patchSettings[i].patch.timbreGroup * 64 + mt32ram.patchSettings[i].patch.timbreNum; + char timbreName[11]; + memcpy(timbreName, mt32ram.timbres[absTimbreNum].timbre.common.name, 10); + timbreName[10] = 0; + printDebug("WRITE-PARTPATCH (%d-%d@%d..%d): %d; timbre=%d (%s), outlevel=%d", first, last, off, off + len, i, absTimbreNum, timbreName, mt32ram.patchSettings[i].outlevel); + if (parts[i] != NULL) { + if (i != 8) { + // Note: Confirmed on CM-64 that we definitely *should* update the timbre here, + // but only in the case that the sysex actually writes to those values + if (i == first && off > 2) { + printDebug(" (Not updating timbre, since those values weren't touched)"); + } else { + parts[i]->setTimbre(&mt32ram.timbres[parts[i]->getAbsTimbreNum()].timbre); + } + } + parts[i]->refresh(); + } + } + break; + case MR_RhythmTemp: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + int timbreNum = mt32ram.rhythmSettings[i].timbre; + char timbreName[11]; + if (timbreNum < 94) { + memcpy(timbreName, mt32ram.timbres[128 + timbreNum].timbre.common.name, 10); + timbreName[10] = 0; + } else { + strcpy(timbreName, "[None]"); + } + printDebug("WRITE-RHYTHM (%d-%d@%d..%d): %d; level=%02x, panpot=%02x, reverb=%02x, timbre=%d (%s)", first, last, off, off + len, i, mt32ram.rhythmSettings[i].outlevel, mt32ram.rhythmSettings[i].panpot, mt32ram.rhythmSettings[i].reverbSwitch, mt32ram.rhythmSettings[i].timbre, timbreName); + } + if (parts[8] != NULL) { + parts[8]->refresh(); + } + break; + case MR_TimbreTemp: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.timbreSettings[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + char instrumentName[11]; + memcpy(instrumentName, mt32ram.timbreSettings[i].common.name, 10); + instrumentName[10] = 0; + printDebug("WRITE-PARTTIMBRE (%d-%d@%d..%d): timbre=%d (%s)", first, last, off, off + len, i, instrumentName); + if (parts[i] != NULL) { + parts[i]->refresh(); + } + } + break; + case MR_Patches: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.patches[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + PatchParam *patch = &mt32ram.patches[i]; + int patchAbsTimbreNum = patch->timbreGroup * 64 + patch->timbreNum; + char instrumentName[11]; + memcpy(instrumentName, mt32ram.timbres[patchAbsTimbreNum].timbre.common.name, 10); + instrumentName[10] = 0; + Bit8u *n = (Bit8u *)patch; + printDebug("WRITE-PATCH (%d-%d@%d..%d): %d; timbre=%d (%s) %02X%02X%02X%02X%02X%02X%02X%02X", first, last, off, off + len, i, patchAbsTimbreNum, instrumentName, n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7]); + // FIXME:KG: The below is definitely dodgy. We just guess that this is the patch that the part was using + // based on a timbre match (but many patches could have the same timbre!) + // If this refresh is really correct, we should store the patch number in use by each part. + /* + for (int part = 0; part < 8; part++) { + if (parts[part] != NULL) { + int partPatchAbsTimbreNum = mt32ram.patchSettings[part].patch.timbreGroup * 64 + mt32ram.patchSettings[part].patch.timbreNum; + if (parts[part]->getAbsTimbreNum() == patchAbsTimbreNum) { + parts[part]->setPatch(patch); + parts[part]->RefreshPatch(); + } + } + } + */ + } + break; + case MR_Timbres: + // Timbres + first += 128; + last += 128; + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.timbres[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + char instrumentName[11]; + memcpy(instrumentName, mt32ram.timbres[i].timbre.common.name, 10); + instrumentName[10] = 0; + printDebug("WRITE-TIMBRE (%d-%d@%d..%d): %d; name=\"%s\"", first, last, off, off + len, i, instrumentName); + // FIXME:KG: Not sure if the stuff below should be done (for rhythm and/or parts)... + // Does the real MT-32 automatically do this? + for (unsigned int part = 0; part < 9; part++) { + if (parts[part] != NULL) { + parts[part]->refreshTimbre(i); + } + } + } + break; + case MR_System: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.system)[m + off] = data[m]; + + report(ReportType_devReconfig, NULL); + + printDebug("WRITE-SYSTEM:"); + refreshSystem(); + break; + case MR_Display: + char buf[MAX_SYSEX_SIZE]; + memcpy(&buf, &data[0], len); + buf[len] = 0; + printDebug("WRITE-LCD: %s", buf); + report(ReportType_lcdMessage, buf); + break; + case MR_Reset: + printDebug("RESET"); + report(ReportType_devReset, NULL); + partialManager->deactivateAll(); + mt32ram = mt32default; + for (int i = 0; i < 9; i++) { + parts[i]->refresh(); + } + isEnabled = false; + break; + } +} + +bool Synth::refreshSystem() { + memset(chantable, -1, sizeof(chantable)); + + for (unsigned int i = 0; i < 9; i++) { + //LOG(LOG_MISC|LOG_ERROR,"Part %d set to MIDI channel %d",i,mt32ram.system.chanAssign[i]); + if (mt32ram.system.chanAssign[i] == 16 && parts[i] != NULL) { + parts[i]->allSoundOff(); + } else { + chantable[(int)mt32ram.system.chanAssign[i]] = (char)i; + } + } + //FIXME:KG: This is just an educated guess. + // The LAPC-I documentation claims a range of 427.5Hz-452.6Hz (similar to what we have here) + // The MT-32 documentation claims a range of 432.1Hz-457.6Hz + masterTune = 440.0f * powf(2.0f, (mt32ram.system.masterTune - 64.0f) / (128.0f * 12.0f)); + printDebug(" Master Tune: %f", (double)masterTune); + printDebug(" Reverb: mode=%d, time=%d, level=%d", mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel); + report(ReportType_newReverbMode, &mt32ram.system.reverbMode); + report(ReportType_newReverbTime, &mt32ram.system.reverbTime); + report(ReportType_newReverbLevel, &mt32ram.system.reverbLevel); + + if (myProp.useDefaultReverb) { + initReverb(mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel); + } else { + initReverb(myProp.reverbType, myProp.reverbTime, mt32ram.system.reverbLevel); + } + + Bit8u *rset = mt32ram.system.reserveSettings; + printDebug(" Partial reserve: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]); + int pr = partialManager->setReserve(rset); + if (pr != 32) + printDebug(" (Partial Reserve Table with less than 32 partials reserved!)"); + rset = mt32ram.system.chanAssign; + printDebug(" Part assign: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]); + printDebug(" Master volume: %d", mt32ram.system.masterVol); + masterVolume = (Bit16u)(mt32ram.system.masterVol * 32767 / 100); + if (!tables.init(this, pcmWaves, (float)myProp.sampleRate, masterTune)) { + report(ReportType_errorSampleRate, NULL); + return false; + } + return true; +} + +bool Synth::dumpTimbre(File *file, const TimbreParam *timbre, Bit32u address) { + // Sysex header + if (!file->writeBit8u(0xF0)) + return false; + if (!file->writeBit8u(0x41)) + return false; + if (!file->writeBit8u(0x10)) + return false; + if (!file->writeBit8u(0x16)) + return false; + if (!file->writeBit8u(0x12)) + return false; + + char lsb = (char)(address & 0x7f); + char isb = (char)((address >> 7) & 0x7f); + char msb = (char)(((address >> 14) & 0x7f) | 0x08); + + //Address + if (!file->writeBit8u(msb)) + return false; + if (!file->writeBit8u(isb)) + return false; + if (!file->writeBit8u(lsb)) + return false; + + //Data + if (file->write(timbre, 246) != 246) + return false; + + //Checksum + unsigned char checksum = calcSysexChecksum((const Bit8u *)timbre, 246, msb + isb + lsb); + if (!file->writeBit8u(checksum)) + return false; + + //End of sysex + if (!file->writeBit8u(0xF7)) + return false; + return true; +} + +int Synth::dumpTimbres(const char *filename, int start, int len) { + File *file = openFile(filename, File::OpenMode_write); + if (file == NULL) + return -1; + + for (int timbreNum = start; timbreNum < start + len; timbreNum++) { + int useaddr = (timbreNum - start) * 256; + TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; + if (!dumpTimbre(file, timbre, useaddr)) + break; + } + closeFile(file); + return 0; +} + +void ProduceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) { +#if MT32EMU_USE_MMX > 2 + //FIXME:KG: This appears to introduce crackle + int donelen = i386_produceOutput1(useBuf, stream, len, volume); + len -= donelen; + stream += donelen * 2; + useBuf += donelen * 2; +#endif + int end = len * 2; + while (end--) { + *stream = *stream + (Bit16s)(((Bit32s)*useBuf++ * (Bit32s)volume)>>15); + stream++; + } +} + +void Synth::render(Bit16s *stream, Bit32u len) { + memset(stream, 0, len * sizeof (Bit16s) * 2); + if (!isEnabled) + return; + while (len > 0) { + Bit32u thisLen = len > MAX_SAMPLE_OUTPUT ? MAX_SAMPLE_OUTPUT : len; + doRender(stream, thisLen); + len -= thisLen; + stream += 2 * thisLen; + } +} + +void Synth::doRender(Bit16s *stream, Bit32u len) { + partialManager->ageAll(); + + if (myProp.useReverb) { + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialManager->shouldReverb(i)) { + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) { + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); + } + } + } + Bit32u m = 0; + for (unsigned int i = 0; i < len; i++) { + sndbufl[i] = (float)stream[m] / 32767.0f; + m++; + sndbufr[i] = (float)stream[m] / 32767.0f; + m++; + } + reverbModel->processreplace(sndbufl, sndbufr, outbufl, outbufr, len, 1); + m=0; + for (unsigned int i = 0; i < len; i++) { + stream[m] = (Bit16s)(outbufl[i] * 32767.0f); + m++; + stream[m] = (Bit16s)(outbufr[i] * 32767.0f); + m++; + } + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (!partialManager->shouldReverb(i)) { + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) { + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); + } + } + } + } else { + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); + } + } + + partialManager->clearAlreadyOutputed(); + +#if MT32EMU_MONITOR_PARTIALS == 1 + samplepos += len; + if (samplepos > myProp.SampleRate * 5) { + samplepos = 0; + int partialUsage[9]; + partialManager->GetPerPartPartialUsage(partialUsage); + printDebug("1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d", partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7]); + printDebug("Rhythm: %02d TOTAL: %02d", partialUsage[8], MT32EMU_MAX_PARTIALS - partialManager->GetFreePartialCount()); + } +#endif +} + +const Partial *Synth::getPartial(unsigned int partialNum) const { + return partialManager->getPartial(partialNum); +} + +const Part *Synth::getPart(unsigned int partNum) const { + if (partNum > 8) + return NULL; + return parts[partNum]; +} + +} diff --git a/audio/softsynth/mt32/synth.h b/audio/softsynth/mt32/synth.h new file mode 100644 index 0000000000..3fc303d322 --- /dev/null +++ b/audio/softsynth/mt32/synth.h @@ -0,0 +1,300 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_SYNTH_H +#define MT32EMU_SYNTH_H + +#include "common/scummsys.h" + +class revmodel; + +namespace MT32Emu { + +class File; +class TableInitialiser; +class Partial; +class PartialManager; +class Part; + +enum ReportType { + // Errors + ReportType_errorControlROM = 1, + ReportType_errorPCMROM, + ReportType_errorSampleRate, + + // Progress + ReportType_progressInit, + + // HW spec + ReportType_availableSSE, + ReportType_available3DNow, + ReportType_usingSSE, + ReportType_using3DNow, + + // General info + ReportType_lcdMessage, + ReportType_devReset, + ReportType_devReconfig, + ReportType_newReverbMode, + ReportType_newReverbTime, + ReportType_newReverbLevel +}; + +struct SynthProperties { + // Sample rate to use in mixing + int sampleRate; + + // Flag to activate reverb. True = use reverb, False = no reverb + bool useReverb; + // True to use software set reverb settings, False to set reverb settings in + // following parameters + bool useDefaultReverb; + // When not using the default settings, this specifies one of the 4 reverb types + // 1 = Room 2 = Hall 3 = Plate 4 = Tap + unsigned char reverbType; + // This specifies the delay time, from 0-7 (not sure of the actual MT-32's measurement) + unsigned char reverbTime; + // This specifies the reverb level, from 0-7 (not sure of the actual MT-32's measurement) + unsigned char reverbLevel; + // The name of the directory in which the ROM and data files are stored (with trailing slash/backslash) + // Not used if "openFile" is set. May be NULL in any case. + char *baseDir; + // This is used as the first argument to all callbacks + void *userData; + // Callback for reporting various errors and information. May be NULL + int (*report)(void *userData, ReportType type, const void *reportData); + // Callback for debug messages, in vprintf() format + void (*printDebug)(void *userData, const char *fmt, va_list list); + // Callback for providing an implementation of File, opened and ready for use + // May be NULL, in which case a default implementation will be used. + File *(*openFile)(void *userData, const char *filename, File::OpenMode mode); + // Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted. + void (*closeFile)(void *userData, File *file); +}; + +// This is the specification of the Callback routine used when calling the RecalcWaveforms +// function +typedef void (*recalcStatusCallback)(int percDone); + +// This external function recreates the base waveform file (waveforms.raw) using a specifed +// sampling rate. The callback routine provides interactivity to let the user know what +// percentage is complete in regenerating the waveforms. When a NULL pointer is used as the +// callback routine, no status is reported. +bool RecalcWaveforms(char * baseDir, int sampRate, recalcStatusCallback callBack); + +typedef float (*iir_filter_type)(float input,float *hist1_ptr, float *coef_ptr); + +const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41; + +const Bit8u SYSEX_MDL_MT32 = 0x16; +const Bit8u SYSEX_MDL_D50 = 0x14; + +const Bit8u SYSEX_CMD_RQ1 = 0x11; // Request data #1 +const Bit8u SYSEX_CMD_DT1 = 0x12; // Data set 1 +const Bit8u SYSEX_CMD_WSD = 0x40; // Want to send data +const Bit8u SYSEX_CMD_RQD = 0x41; // Request data +const Bit8u SYSEX_CMD_DAT = 0x42; // Data set +const Bit8u SYSEX_CMD_ACK = 0x43; // Acknowledge +const Bit8u SYSEX_CMD_EOD = 0x45; // End of data +const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error +const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection + +const unsigned int CONTROL_ROM_SIZE = 64 * 1024; + +struct ControlROMPCMStruct +{ + Bit8u pos; + Bit8u len; + Bit8u pitchLSB; + Bit8u pitchMSB; +}; + +struct ControlROMMap { + Bit16u idPos; + Bit16u idLen; + const char *idBytes; + Bit16u pcmTable; + Bit16u pcmCount; + Bit16u timbreAMap; + Bit16u timbreAOffset; + Bit16u timbreBMap; + Bit16u timbreBOffset; + Bit16u timbreRMap; + Bit16u timbreRCount; + Bit16u rhythmSettings; + Bit16u rhythmSettingsCount; + Bit16u reserveSettings; + Bit16u panSettings; + Bit16u programSettings; +}; + +enum MemoryRegionType { + MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset +}; + +class MemoryRegion { +public: + MemoryRegionType type; + Bit32u startAddr, entrySize, entries; + + int lastTouched(Bit32u addr, Bit32u len) const { + return (offset(addr) + len - 1) / entrySize; + } + int firstTouchedOffset(Bit32u addr) const { + return offset(addr) % entrySize; + } + int firstTouched(Bit32u addr) const { + return offset(addr) / entrySize; + } + Bit32u regionEnd() const { + return startAddr + entrySize * entries; + } + bool contains(Bit32u addr) const { + return addr >= startAddr && addr < regionEnd(); + } + int offset(Bit32u addr) const { + return addr - startAddr; + } + Bit32u getClampedLen(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) + return regionEnd() - addr; + return len; + } + Bit32u next(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) { + return regionEnd() - addr; + } + return 0; + } +}; + + +class Synth { +friend class Part; +friend class RhythmPart; +friend class Partial; +friend class Tables; +private: + bool isEnabled; + + iir_filter_type iirFilter; + + PCMWaveEntry *pcmWaves; // Array + + const ControlROMMap *controlROMMap; + Bit8u controlROMData[CONTROL_ROM_SIZE]; + Bit16s *pcmROMData; + int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM + + Bit8s chantable[32]; + + #if MT32EMU_MONITOR_PARTIALS == 1 + static Bit32s samplepos = 0; + #endif + + Tables tables; + + MemParams mt32ram, mt32default; + + revmodel *reverbModel; + + float masterTune; + Bit16u masterVolume; + + bool isOpen; + + PartialManager *partialManager; + Part *parts[9]; + + Bit16s tmpBuffer[MAX_SAMPLE_OUTPUT * 2]; + float sndbufl[MAX_SAMPLE_OUTPUT]; + float sndbufr[MAX_SAMPLE_OUTPUT]; + float outbufl[MAX_SAMPLE_OUTPUT]; + float outbufr[MAX_SAMPLE_OUTPUT]; + + SynthProperties myProp; + + bool loadPreset(File *file); + void initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel); + void doRender(Bit16s * stream, Bit32u len); + + void playAddressedSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data); + void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data); + + bool loadControlROM(const char *filename); + bool loadPCMROM(const char *filename); + bool dumpTimbre(File *file, const TimbreParam *timbre, Bit32u addr); + int dumpTimbres(const char *filename, int start, int len); + + bool initPCMList(Bit16u mapAddress, Bit16u count); + bool initRhythmTimbres(Bit16u mapAddress, Bit16u count); + bool initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre); + bool initRhythmTimbre(int drumNum, const Bit8u *mem, unsigned int memLen); + bool refreshSystem(); + +protected: + int report(ReportType type, const void *reportData); + File *openFile(const char *filename, File::OpenMode mode); + void closeFile(File *file); + void printDebug(const char *fmt, ...) GCC_PRINTF(2, 3); + +public: + static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum); + + Synth(); + ~Synth(); + + // Used to initialise the MT-32. Must be called before any other function. + // Returns true if initialization was sucessful, otherwise returns false. + bool open(SynthProperties &useProp); + + // Closes the MT-32 and deallocates any memory used by the synthesizer + void close(void); + + // Sends a 4-byte MIDI message to the MT-32 for immediate playback + void playMsg(Bit32u msg); + void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity); + + // Sends a string of Sysex commands to the MT-32 for immediate interpretation + // The length is in bytes + void playSysex(const Bit8u *sysex, Bit32u len); + void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len); + void playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len); + void writeSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + + // This callback routine is used to have the MT-32 generate samples to the specified + // output stream. The length is in whole samples, not bytes. (I.E. in 16-bit stereo, + // one sample is 4 bytes) + void render(Bit16s * stream, Bit32u len); + + const Partial *getPartial(unsigned int partialNum) const; + + void readMemory(Bit32u addr, Bit32u len, Bit8u *data); + + // partNum should be 0..7 for Part 1..8, or 8 for Rhythm + const Part *getPart(unsigned int partNum) const; +}; + +} + +#endif diff --git a/audio/softsynth/mt32/tables.cpp b/audio/softsynth/mt32/tables.cpp new file mode 100644 index 0000000000..eba4d2a520 --- /dev/null +++ b/audio/softsynth/mt32/tables.cpp @@ -0,0 +1,757 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "mt32emu.h" + +#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__) +// Older versions of Mac OS X didn't supply a powf function, so using it +// will cause a binary incompatibility when trying to run a binary built +// on a newer OS X release on an olderr one. And Solaris 8 doesn't provide +// powf, floorf, fabsf etc. at all. +// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in +// libstdc++. math/stubs.o should be empty, but it comes with a symbol for +// powf, resulting in a linker error because of multiple definitions. +// Hence we re-define them here. The only potential drawback is that it +// might be a little bit slower this way. +#define powf(x,y) ((float)pow(x,y)) +#define floorf(x) ((float)floor(x)) +#define fabsf(x) ((float)fabs(x)) +#endif + +#define FIXEDPOINT_MAKE(x, point) ((Bit32u)((1 << point) * x)) + +namespace MT32Emu { + +//Amplitude time velocity follow exponential coefficients +static const double tvcatconst[5] = {0.0, 0.002791309, 0.005942882, 0.012652792, 0.026938637}; +static const double tvcatmult[5] = {1.0, 1.072662811, 1.169129367, 1.288579123, 1.229630539}; + +// These are division constants for the TVF depth key follow +static const Bit32u depexp[5] = {3000, 950, 485, 255, 138}; + +//Envelope time keyfollow exponential coefficients +static const double tkcatconst[5] = {0.0, 0.005853144, 0.011148054, 0.019086143, 0.043333215}; +static const double tkcatmult[5] = {1.0, 1.058245688, 1.048488989, 1.016049301, 1.097538067}; + +// Begin filter stuff + +// Pre-warp the coefficients of a numerator or denominator. +// Note that a0 is assumed to be 1, so there is no wrapping +// of it. +static void prewarp(double *a1, double *a2, double fc, double fs) { + double wp; + + wp = 2.0 * fs * tan(DOUBLE_PI * fc / fs); + + *a2 = *a2 / (wp * wp); + *a1 = *a1 / wp; +} + +// Transform the numerator and denominator coefficients +// of s-domain biquad section into corresponding +// z-domain coefficients. +// +// Store the 4 IIR coefficients in array pointed by coef +// in following order: +// beta1, beta2 (denominator) +// alpha1, alpha2 (numerator) +// +// Arguments: +// a0-a2 - s-domain numerator coefficients +// b0-b2 - s-domain denominator coefficients +// k - filter gain factor. initially set to 1 +// and modified by each biquad section in such +// a way, as to make it the coefficient by +// which to multiply the overall filter gain +// in order to achieve a desired overall filter gain, +// specified in initial value of k. +// fs - sampling rate (Hz) +// coef - array of z-domain coefficients to be filled in. +// +// Return: +// On return, set coef z-domain coefficients +static void bilinear(double a0, double a1, double a2, double b0, double b1, double b2, double *k, double fs, float *coef) { + double ad, bd; + + // alpha (Numerator in s-domain) + ad = 4. * a2 * fs * fs + 2. * a1 * fs + a0; + // beta (Denominator in s-domain) + bd = 4. * b2 * fs * fs + 2. * b1* fs + b0; + + // update gain constant for this section + *k *= ad/bd; + + // Denominator + *coef++ = (float)((2. * b0 - 8. * b2 * fs * fs) / bd); // beta1 + *coef++ = (float)((4. * b2 * fs * fs - 2. * b1 * fs + b0) / bd); // beta2 + + // Nominator + *coef++ = (float)((2. * a0 - 8. * a2 * fs * fs) / ad); // alpha1 + *coef = (float)((4. * a2 * fs * fs - 2. * a1 * fs + a0) / ad); // alpha2 +} + +// a0-a2: numerator coefficients +// b0-b2: denominator coefficients +// fc: Filter cutoff frequency +// fs: sampling rate +// k: overall gain factor +// coef: pointer to 4 iir coefficients +static void szxform(double *a0, double *a1, double *a2, double *b0, double *b1, double *b2, double fc, double fs, double *k, float *coef) { + // Calculate a1 and a2 and overwrite the original values + prewarp(a1, a2, fc, fs); + prewarp(b1, b2, fc, fs); + bilinear(*a0, *a1, *a2, *b0, *b1, *b2, k, fs, coef); +} + +static void initFilter(float fs, float fc, float *icoeff, float Q) { + float *coef; + double a0, a1, a2, b0, b1, b2; + + double k = 1.5; // Set overall filter gain factor + coef = icoeff + 1; // Skip k, or gain + + // Section 1 + a0 = 1.0; + a1 = 0; + a2 = 0; + b0 = 1.0; + b1 = 0.765367 / Q; // Divide by resonance or Q + b2 = 1.0; + szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef); + coef += 4; // Point to next filter section + + // Section 2 + a0 = 1.0; + a1 = 0; + a2 = 0; + b0 = 1.0; + b1 = 1.847759 / Q; + b2 = 1.0; + szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef); + + icoeff[0] = (float)k; +} + +void Tables::initFiltCoeff(float samplerate) { + for (int j = 0; j < FILTERGRAN; j++) { + for (int res = 0; res < 31; res++) { + float tres = resonanceFactor[res]; + initFilter((float)samplerate, (((float)(j+1.0)/FILTERGRAN)) * ((float)samplerate/2), filtCoeff[j][res], tres); + } + } +} + +void Tables::initEnvelopes(float samplerate) { + for (int lf = 0; lf <= 100; lf++) { + float elf = (float)lf; + + // General envelope + // This formula fits observation of the CM-32L by +/- 0.03s or so for the second time value in the filter, + // when all other times were 0 and all levels were 100. Note that variations occur depending on the level + // delta of the section, which we're not fully emulating. + float seconds = powf(2.0f, (elf / 8.0f) + 7.0f) / 32768.0f; + int samples = (int)(seconds * samplerate); + envTime[lf] = samples; + + // Cap on envelope times depending on the level delta + if (elf == 0) { + envDeltaMaxTime[lf] = 63; + } else { + float cap = 11.0f * (float)log(elf) + 64; + if (cap > 100.0f) { + cap = 100.0f; + } + envDeltaMaxTime[lf] = (int)cap; + } + + + // This (approximately) represents the time durations when the target level is 0. + // Not sure why this is a special case, but it's seen to be from the real thing. + seconds = powf(2, (elf / 8.0f) + 6) / 32768.0f; + envDecayTime[lf] = (int)(seconds * samplerate); + + // I am certain of this: Verified by hand LFO log + lfoPeriod[lf] = (Bit32u)(((float)samplerate) / (powf(1.088883372f, (float)lf) * 0.021236044f)); + } +} + +void Tables::initMT32ConstantTables(Synth *synth) { + int lf; + synth->printDebug("Initialising Pitch Tables"); + for (lf = -108; lf <= 108; lf++) { + tvfKeyfollowMult[lf + 108] = (int)(256 * powf(2.0f, (float)(lf / 24.0f))); + //synth->printDebug("KT %d = %d", f, keytable[f+108]); + } + + for (int res = 0; res < 31; res++) { + resonanceFactor[res] = powf((float)res / 30.0f, 5.0f) + 1.0f; + } + + int period = 65536; + + for (int ang = 0; ang < period; ang++) { + int halfang = (period / 2); + int angval = ang % halfang; + float tval = (((float)angval / (float)halfang) - 0.5f) * 2; + if (ang >= halfang) + tval = -tval; + sintable[ang] = (Bit16s)(tval * 50.0f) + 50; + } + + int velt, dep; + float tempdep; + for (velt = 0; velt < 128; velt++) { + for (dep = 0; dep < 5; dep++) { + if (dep > 0) { + float ff = (float)(exp(3.5f * tvcatconst[dep] * (59.0f - (float)velt)) * tvcatmult[dep]); + tempdep = 256.0f * ff; + envTimeVelfollowMult[dep][velt] = (int)tempdep; + //if ((velt % 16) == 0) { + // synth->printDebug("Key %d, depth %d, factor %d", velt, dep, (int)tempdep); + //} + } else + envTimeVelfollowMult[dep][velt] = 256; + } + + for (dep = -7; dep < 8; dep++) { + float fldep = (float)abs(dep) / 7.0f; + fldep = powf(fldep,2.5f); + if (dep < 0) + fldep = fldep * -1.0f; + pwVelfollowAdd[dep+7][velt] = Bit32s((fldep * (float)velt * 100) / 128.0); + } + } + + for (dep = 0; dep <= 100; dep++) { + for (velt = 0; velt < 128; velt++) { + float fdep = (float)dep * 0.000347013f; // Another MT-32 constant + float fv = ((float)velt - 64.0f)/7.26f; + float flogdep = powf(10, fdep * fv); + float fbase; + + if (velt > 64) + synth->tables.tvfVelfollowMult[velt][dep] = (int)(flogdep * 256.0); + else { + //lff = 1 - (pow(((128.0 - (float)lf) / 64.0),.25) * ((float)velt / 96)); + fbase = 1 - (powf(((float)dep / 100.0f),.25f) * ((float)(64-velt) / 96.0f)); + synth->tables.tvfVelfollowMult[velt][dep] = (int)(fbase * 256.0); + } + //synth->printDebug("Filvel dep %d velt %d = %x", dep, velt, filveltable[velt][dep]); + } + } + + for (lf = 0; lf < 128; lf++) { + float veloFract = lf / 127.0f; + for (int velsens = 0; velsens <= 100; velsens++) { + float sensFract = (velsens - 50) / 50.0f; + if (velsens < 50) { + tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, veloFract * -sensFract * 127.0f / 20.0f), 8); + } else { + tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, (1.0f - veloFract) * sensFract * 127.0f / 20.0f), 8); + } + } + } + + for (lf = 0; lf <= 100; lf++) { + // Converts the 0-100 range used by the MT-32 to volume multiplier + volumeMult[lf] = FIXEDPOINT_MAKE(powf((float)lf / 100.0f, FLOAT_LN), 7); + } + + for (lf = 0; lf <= 100; lf++) { + float mv = lf / 100.0f; + float pt = mv - 0.5f; + if (pt < 0) + pt = 0; + + // Original (CC version) + //pwFactor[lf] = (int)(pt * 210.04f) + 128; + + // Approximation from sample comparison + pwFactor[lf] = (int)(pt * 179.0f) + 128; + } + + for (unsigned int i = 0; i < MAX_SAMPLE_OUTPUT; i++) { + int myRand; + myRand = rand(); + //myRand = ((myRand - 16383) * 7168) >> 16; + // This one is slower but works with all values of RAND_MAX + myRand = (int)((myRand - RAND_MAX / 2) / (float)RAND_MAX * (7168 / 2)); + //FIXME:KG: Original ultimately set the lowest two bits to 0, for no obvious reason + noiseBuf[i] = (Bit16s)myRand; + } + + float tdist; + float padjtable[51]; + for (lf = 0; lf <= 50; lf++) { + if (lf == 0) + padjtable[lf] = 7; + else if (lf == 1) + padjtable[lf] = 6; + else if (lf == 2) + padjtable[lf] = 5; + else if (lf == 3) + padjtable[lf] = 4; + else if (lf == 4) + padjtable[lf] = 4 - (0.333333f); + else if (lf == 5) + padjtable[lf] = 4 - (0.333333f * 2); + else if (lf == 6) + padjtable[lf] = 3; + else if ((lf > 6) && (lf <= 12)) { + tdist = (lf-6.0f) / 6.0f; + padjtable[lf] = 3.0f - tdist; + } else if ((lf > 12) && (lf <= 25)) { + tdist = (lf - 12.0f) / 13.0f; + padjtable[lf] = 2.0f - tdist; + } else { + tdist = (lf - 25.0f) / 25.0f; + padjtable[lf] = 1.0f - tdist; + } + //synth->printDebug("lf %d = padj %f", lf, (double)padjtable[lf]); + } + + float lfp, depf, finalval, tlf; + int depat, pval, depti; + for (lf = 0; lf <= 10; lf++) { + // I believe the depth is cubed or something + + for (depat = 0; depat <= 100; depat++) { + if (lf > 0) { + depti = abs(depat - 50); + tlf = (float)lf - padjtable[depti]; + if (tlf < 0) + tlf = 0; + lfp = (float)exp(0.713619942f * tlf) / 407.4945111f; + + if (depat < 50) + finalval = 4096.0f * powf(2, -lfp); + else + finalval = 4096.0f * powf(2, lfp); + pval = (int)finalval; + + pitchEnvVal[lf][depat] = pval; + //synth->printDebug("lf %d depat %d pval %d tlf %f lfp %f", lf,depat,pval, (double)tlf, (double)lfp); + } else { + pitchEnvVal[lf][depat] = 4096; + //synth->printDebug("lf %d depat %d pval 4096", lf, depat); + } + } + } + for (lf = 0; lf <= 100; lf++) { + // It's linear - verified on MT-32 - one of the few things linear + lfp = ((float)lf * 0.1904f) / 310.55f; + + for (depat = 0; depat <= 100; depat++) { + depf = ((float)depat - 50.0f) / 50.0f; + //finalval = pow(2, lfp * depf * .5); + finalval = 4096.0f + (4096.0f * lfp * depf); + + pval = (int)finalval; + + lfoShift[lf][depat] = pval; + + //synth->printDebug("lf %d depat %d pval %x", lf,depat,pval); + } + } + + for (lf = 0; lf <= 12; lf++) { + for (int distval = 0; distval < 128; distval++) { + float amplog, dval; + if (lf == 0) { + amplog = 0; + dval = 1; + tvaBiasMult[lf][distval] = 256; + } else { + /* + amplog = powf(1.431817011f, (float)lf) / FLOAT_PI; + dval = ((128.0f - (float)distval) / 128.0f); + amplog = exp(amplog); + dval = powf(amplog, dval) / amplog; + tvaBiasMult[lf][distval] = (int)(dval * 256.0); + */ + // Lets assume for a second it's linear + + // Distance of full volume reduction + amplog = (float)(12.0f / (float)lf) * 24.0f; + if (distval > amplog) { + tvaBiasMult[lf][distval] = 0; + } else { + dval = (amplog - (float)distval) / amplog; + tvaBiasMult[lf][distval] = (int)(dval * 256.0f); + } + } + //synth->printDebug("Ampbias lf %d distval %d = %f (%x) %f", lf, distval, (double)dval, tvaBiasMult[lf][distval],(double)amplog); + } + } + + for (lf = 0; lf <= 14; lf++) { + for (int distval = 0; distval < 128; distval++) { + float filval = fabsf((float)((lf - 7) * 12) / 7.0f); + float amplog, dval; + if (lf == 7) { + amplog = 0; + dval = 1; + tvfBiasMult[lf][distval] = 256; + } else { + //amplog = pow(1.431817011, filval) / FLOAT_PI; + amplog = powf(1.531817011f, filval) / FLOAT_PI; + dval = (128.0f - (float)distval) / 128.0f; + amplog = (float)exp(amplog); + dval = powf(amplog,dval)/amplog; + if (lf < 8) { + tvfBiasMult[lf][distval] = (int)(dval * 256.0f); + } else { + dval = powf(dval, 0.3333333f); + if (dval < 0.01f) + dval = 0.01f; + dval = 1 / dval; + tvfBiasMult[lf][distval] = (int)(dval * 256.0f); + } + } + //synth->printDebug("Fbias lf %d distval %d = %f (%x) %f", lf, distval, (double)dval, tvfBiasMult[lf][distval],(double)amplog); + } + } +} + +// Per-note table initialisation follows + +static void initSaw(NoteLookup *noteLookup, Bit32s div2) { + int tmpdiv = div2 << 16; + for (int rsaw = 0; rsaw <= 100; rsaw++) { + float fsaw; + if (rsaw < 50) + fsaw = 50.0f; + else + fsaw = (float)rsaw; + + //(66 - (((A8 - 50) / 50) ^ 0.63) * 50) / 132 + float sawfact = (66.0f - (powf((fsaw - 50.0f) / 50.0f, 0.63f) * 50.0f)) / 132.0f; + noteLookup->sawTable[rsaw] = (int)(sawfact * (float)tmpdiv) >> 16; + //synth->printDebug("F %d divtable %d saw %d sawtable %d", f, div, rsaw, sawtable[f][rsaw]); + } +} + +static void initDep(KeyLookup *keyLookup, float f) { + for (int dep = 0; dep < 5; dep++) { + if (dep == 0) { + keyLookup->envDepthMult[dep] = 256; + keyLookup->envTimeMult[dep] = 256; + } else { + float depfac = 3000.0f; + float ff, tempdep; + depfac = (float)depexp[dep]; + + ff = (f - (float)MIDDLEC) / depfac; + tempdep = powf(2, ff) * 256.0f; + keyLookup->envDepthMult[dep] = (int)tempdep; + + ff = (float)(exp(tkcatconst[dep] * ((float)MIDDLEC - f)) * tkcatmult[dep]); + keyLookup->envTimeMult[dep] = (int)(ff * 256.0f); + } + } + //synth->printDebug("F %f d1 %x d2 %x d3 %x d4 %x d5 %x", (double)f, noteLookup->fildepTable[0], noteLookup->fildepTable[1], noteLookup->fildepTable[2], noteLookup->fildepTable[3], noteLookup->fildepTable[4]); +} + +Bit16s Tables::clampWF(Synth *synth, const char *n, float ampVal, double input) { + Bit32s x = (Bit32s)(input * ampVal); + if (x < -ampVal - 1) { + synth->printDebug("%s==%d<-WGAMP-1!", n, x); + x = (Bit32s)(-ampVal - 1); + } else if (x > ampVal) { + synth->printDebug("%s==%d>WGAMP!", n, x); + x = (Bit32s)ampVal; + } + return (Bit16s)x; +} + +File *Tables::initWave(Synth *synth, NoteLookup *noteLookup, float ampVal, float div2, File *file) { + int iDiv2 = (int)div2; + noteLookup->waveformSize[0] = iDiv2 << 1; + noteLookup->waveformSize[1] = iDiv2 << 1; + noteLookup->waveformSize[2] = iDiv2 << 2; + for (int i = 0; i < 3; i++) { + if (noteLookup->waveforms[i] == NULL) { + noteLookup->waveforms[i] = new Bit16s[noteLookup->waveformSize[i]]; + } + } + if (file != NULL) { + for (int i = 0; i < 3 && file != NULL; i++) { + size_t len = noteLookup->waveformSize[i]; + for (unsigned int j = 0; j < len; j++) { + if (!file->readBit16u((Bit16u *)¬eLookup->waveforms[i][j])) { + synth->printDebug("Error reading wave file cache!"); + file->close(); + file = NULL; + break; + } + } + } + } + if (file == NULL) { + double sd = DOUBLE_PI / div2; + + for (int fa = 0; fa < (iDiv2 << 1); fa++) { + // sa ranges from 0 to 2PI + double sa = fa * sd; + + // Calculate a sample for the bandlimited sawtooth wave + double saw = 0.0; + int sincs = iDiv2 >> 1; + double sinus = 1.0; + for (int sincNum = 1; sincNum <= sincs; sincNum++) { + saw += sin(sinus * sa) / sinus; + sinus++; + } + + // This works pretty well + // Multiplied by 0.84 so that the spikes caused by bandlimiting don't overdrive the amplitude + noteLookup->waveforms[0][fa] = clampWF(synth, "saw", ampVal, -saw / (0.5 * DOUBLE_PI) * 0.84); + noteLookup->waveforms[1][fa] = clampWF(synth, "cos", ampVal, -cos(sa / 2.0)); + noteLookup->waveforms[2][fa * 2] = clampWF(synth, "cosoff_0", ampVal, -cos(sa - DOUBLE_PI)); + noteLookup->waveforms[2][fa * 2 + 1] = clampWF(synth, "cosoff_1", ampVal, -cos((sa + (sd / 2)) - DOUBLE_PI)); + } + } + return file; +} + +static void initFiltTable(NoteLookup *noteLookup, float freq, float rate) { + for (int tr = 0; tr <= 200; tr++) { + float ftr = (float)tr; + + // Verified exact on MT-32 + if (tr > 100) + ftr = 100.0f + (powf((ftr - 100.0f) / 100.0f, 3.0f) * 100.0f); + + // I think this is the one + float brsq = powf(10.0f, (tr / 50.0f) - 1.0f); + noteLookup->filtTable[0][tr] = (int)((freq * brsq) / (rate / 2) * FILTERGRAN); + if (noteLookup->filtTable[0][tr]>=((FILTERGRAN*15)/16)) + noteLookup->filtTable[0][tr] = ((FILTERGRAN*15)/16); + + float brsa = powf(10.0f, ((tr / 55.0f) - 1.0f)) / 2.0f; + noteLookup->filtTable[1][tr] = (int)((freq * brsa) / (rate / 2) * FILTERGRAN); + if (noteLookup->filtTable[1][tr]>=((FILTERGRAN*15)/16)) + noteLookup->filtTable[1][tr] = ((FILTERGRAN*15)/16); + } +} + +static void initNFiltTable(NoteLookup *noteLookup, float freq, float rate) { + for (int cf = 0; cf <= 100; cf++) { + float cfmult = (float)cf; + + for (int tf = 0;tf <= 100; tf++) { + float tfadd = (float)tf; + + //float freqsum = exp((cfmult + tfadd) / 30.0f) / 4.0f; + //float freqsum = 0.15f * exp(0.45f * ((cfmult + tfadd) / 10.0f)); + + float freqsum = powf(2.0f, ((cfmult + tfadd) - 40.0f) / 16.0f); + + noteLookup->nfiltTable[cf][tf] = (int)((freq * freqsum) / (rate / 2) * FILTERGRAN); + if (noteLookup->nfiltTable[cf][tf] >= ((FILTERGRAN * 15) / 16)) + noteLookup->nfiltTable[cf][tf] = ((FILTERGRAN * 15) / 16); + } + } +} + +File *Tables::initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float masterTune, PCMWaveEntry *pcmWaves, File *file) { + float freq = (float)(masterTune * pow(2.0, ((double)note - MIDDLEA) / 12.0)); + float div2 = rate * 2.0f / freq; + noteLookup->div2 = (int)div2; + + if (noteLookup->div2 == 0) + noteLookup->div2 = 1; + + initSaw(noteLookup, noteLookup->div2); + + //synth->printDebug("Note %f; freq=%f, div=%f", (double)note, (double)freq, (double) rate / freq); + file = initWave(synth, noteLookup, WGAMP, div2, file); + + // Create the pitch tables + if (noteLookup->wavTable == NULL) + noteLookup->wavTable = new Bit32u[synth->controlROMMap->pcmCount]; + double rateMult = 32000.0 / rate; + double tuner = freq * 65536.0f; + for (int pc = 0; pc < synth->controlROMMap->pcmCount; pc++) { + noteLookup->wavTable[pc] = (int)(tuner / pcmWaves[pc].tune * rateMult); + } + + initFiltTable(noteLookup, freq, rate); + initNFiltTable(noteLookup, freq, rate); + return file; +} + +bool Tables::initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float masterTune) { + const char *NoteNames[12] = { + "C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B " + }; + char filename[64]; + int intRate = (int)rate; + char version[4] = {0, 0, 0, 5}; + sprintf(filename, "waveformcache-%d-%.2f.raw", intRate, (double)masterTune); + + File *file = NULL; + char header[20]; + memcpy(header, "MT32WAVE", 8); + int pos = 8; + // Version... + for (int i = 0; i < 4; i++) + header[pos++] = version[i]; + header[pos++] = (char)((intRate >> 24) & 0xFF); + header[pos++] = (char)((intRate >> 16) & 0xFF); + header[pos++] = (char)((intRate >> 8) & 0xFF); + header[pos++] = (char)(intRate & 0xFF); + int intTuning = (int)masterTune; + header[pos++] = (char)((intTuning >> 8) & 0xFF); + header[pos++] = (char)(intTuning & 0xFF); + header[pos++] = 0; + header[pos] = (char)((masterTune - intTuning) * 10); +#if MT32EMU_WAVECACHEMODE < 2 + bool reading = false; + file = synth->openFile(filename, File::OpenMode_read); + if (file != NULL) { + char fileHeader[20]; + if (file->read(fileHeader, 20) == 20) { + if (memcmp(fileHeader, header, 20) == 0) { + Bit16u endianCheck; + if (file->readBit16u(&endianCheck)) { + if (endianCheck == 1) { + reading = true; + } else { + synth->printDebug("Endian check in %s does not match expected", filename); + } + } else { + synth->printDebug("Unable to read endian check in %s", filename); + } + } else { + synth->printDebug("Header of %s does not match expected", filename); + } + } else { + synth->printDebug("Error reading 16 bytes of %s", filename); + } + if (!reading) { + file->close(); + file = NULL; + } + } else { + synth->printDebug("Unable to open %s for reading", filename); + } +#endif + + float progress = 0.0f; + bool abort = false; + synth->report(ReportType_progressInit, &progress); + for (int f = LOWEST_NOTE; f <= HIGHEST_NOTE; f++) { + synth->printDebug("Initialising note %s%d", NoteNames[f % 12], (f / 12) - 2); + NoteLookup *noteLookup = ¬eLookups[f - LOWEST_NOTE]; + file = initNote(synth, noteLookup, (float)f, rate, masterTune, pcmWaves, file); + progress = (f - LOWEST_NOTE + 1) / (float)NUM_NOTES; + abort = synth->report(ReportType_progressInit, &progress) != 0; + if (abort) + break; + } + +#if MT32EMU_WAVECACHEMODE == 0 || MT32EMU_WAVECACHEMODE == 2 + if (file == NULL) { + file = synth->openFile(filename, File::OpenMode_write); + if (file != NULL) { + if (file->write(header, 20) == 20 && file->writeBit16u(1)) { + for (int f = 0; f < NUM_NOTES; f++) { + for (int i = 0; i < 3 && file != NULL; i++) { + int len = noteLookups[f].waveformSize[i]; + for (int j = 0; j < len; j++) { + if (!file->writeBit16u(noteLookups[f].waveforms[i][j])) { + synth->printDebug("Error writing waveform cache file"); + file->close(); + file = NULL; + break; + } + } + } + } + } else { + synth->printDebug("Error writing 16-byte header to %s - won't continue saving", filename); + } + } else { + synth->printDebug("Unable to open %s for writing - won't be created", filename); + } + } +#endif + + if (file != NULL) + synth->closeFile(file); + return !abort; +} + +void Tables::freeNotes() { + for (int t = 0; t < 3; t++) { + for (int m = 0; m < NUM_NOTES; m++) { + if (noteLookups[m].waveforms[t] != NULL) { + delete[] noteLookups[m].waveforms[t]; + noteLookups[m].waveforms[t] = NULL; + noteLookups[m].waveformSize[t] = 0; + } + if (noteLookups[m].wavTable != NULL) { + delete[] noteLookups[m].wavTable; + noteLookups[m].wavTable = NULL; + } + } + } + initialisedMasterTune = 0.0f; +} + +Tables::Tables() { + initialisedSampleRate = 0.0f; + initialisedMasterTune = 0.0f; + memset(¬eLookups, 0, sizeof(noteLookups)); +} + +bool Tables::init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune) { + if (sampleRate <= 0.0f) { + synth->printDebug("Bad sampleRate (%f <= 0.0f)", (double)sampleRate); + return false; + } + if (initialisedSampleRate == 0.0f) { + initMT32ConstantTables(synth); + } + if (initialisedSampleRate != sampleRate) { + initFiltCoeff(sampleRate); + initEnvelopes(sampleRate); + for (int key = 12; key <= 108; key++) { + initDep(&keyLookups[key - 12], (float)key); + } + } + if (initialisedSampleRate != sampleRate || initialisedMasterTune != masterTune) { + freeNotes(); + if (!initNotes(synth, pcmWaves, sampleRate, masterTune)) { + return false; + } + initialisedSampleRate = sampleRate; + initialisedMasterTune = masterTune; + } + return true; +} + +} diff --git a/audio/softsynth/mt32/tables.h b/audio/softsynth/mt32/tables.h new file mode 100644 index 0000000000..d9af5114b2 --- /dev/null +++ b/audio/softsynth/mt32/tables.h @@ -0,0 +1,116 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_TABLES_H +#define MT32EMU_TABLES_H + +namespace MT32Emu { + +// Mathematical constants +const double DOUBLE_PI = 3.1415926535897932384626433832795; +const double DOUBLE_LN = 2.3025850929940456840179914546844; +const float FLOAT_PI = 3.1415926535897932384626433832795f; +const float FLOAT_LN = 2.3025850929940456840179914546844f; + +// Filter settings +const int FILTERGRAN = 512; + +// Amplitude of waveform generator +// FIXME: This value is the amplitude possible whilst avoiding +// overdriven values immediately after filtering when playing +// back SQ3MT.MID. Needs to be checked. +const int WGAMP = 12382; + +const int MIDDLEC = 60; +const int MIDDLEA = 69; // By this I mean "A above middle C" + +// FIXME:KG: may only need to do 12 to 108 +// 12..108 is the range allowed by note on commands, but the key can be modified by pitch keyfollow +// and adjustment for timbre pitch, so the results can be outside that range. +// Should we move it (by octave) into the 12..108 range, or keep it in 0..127 range, +// or something else altogether? +const int LOWEST_NOTE = 12; +const int HIGHEST_NOTE = 127; +const int NUM_NOTES = HIGHEST_NOTE - LOWEST_NOTE + 1; // Number of slots for note LUT + +class Synth; + +struct NoteLookup { + Bit32u div2; + Bit32u *wavTable; + Bit32s sawTable[101]; + int filtTable[2][201]; + int nfiltTable[101][101]; + Bit16s *waveforms[3]; + Bit32u waveformSize[3]; +}; + +struct KeyLookup { + Bit32s envTimeMult[5]; // For envelope time adjustment for key pressed + Bit32s envDepthMult[5]; +}; + +class Tables { + float initialisedSampleRate; + float initialisedMasterTune; + void initMT32ConstantTables(Synth *synth); + static Bit16s clampWF(Synth *synth, const char *n, float ampVal, double input); + static File *initWave(Synth *synth, NoteLookup *noteLookup, float ampsize, float div2, File *file); + bool initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float tuning); + void initEnvelopes(float sampleRate); + void initFiltCoeff(float samplerate); +public: + // Constant LUTs + Bit32s tvfKeyfollowMult[217]; + Bit32s tvfVelfollowMult[128][101]; + Bit32s tvfBiasMult[15][128]; + Bit32u tvaVelfollowMult[128][101]; + Bit32s tvaBiasMult[13][128]; + Bit16s noiseBuf[MAX_SAMPLE_OUTPUT]; + Bit16s sintable[65536]; + Bit32s pitchEnvVal[16][101]; + Bit32s envTimeVelfollowMult[5][128]; + Bit32s pwVelfollowAdd[15][128]; + float resonanceFactor[31]; + Bit32u lfoShift[101][101]; + Bit32s pwFactor[101]; + Bit32s volumeMult[101]; + + // LUTs varying with sample rate + Bit32u envTime[101]; + Bit32u envDeltaMaxTime[101]; + Bit32u envDecayTime[101]; + Bit32u lfoPeriod[101]; + float filtCoeff[FILTERGRAN][31][8]; + + // Various LUTs for each note and key + NoteLookup noteLookups[NUM_NOTES]; + KeyLookup keyLookups[97]; + + Tables(); + bool init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune); + File *initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float tuning, PCMWaveEntry *pcmWaves, File *file); + void freeNotes(); +}; + +} + +#endif diff --git a/audio/softsynth/opl/dbopl.cpp b/audio/softsynth/opl/dbopl.cpp new file mode 100644 index 0000000000..47e263b6b9 --- /dev/null +++ b/audio/softsynth/opl/dbopl.cpp @@ -0,0 +1,1536 @@ +/* + * Copyright (C) 2002-2010 The DOSBox Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + DOSBox implementation of a combined Yamaha YMF262 and Yamaha YM3812 emulator. + Enabling the opl3 bit will switch the emulator to stereo opl3 output instead of regular mono opl2 + Except for the table generation it's all integer math + Can choose different types of generators, using muls and bigger tables, try different ones for slower platforms + The generation was based on the MAME implementation but tried to have it use less memory and be faster in general + MAME uses much bigger envelope tables and this will be the biggest cause of it sounding different at times + + //TODO Don't delay first operator 1 sample in opl3 mode + //TODO Maybe not use class method pointers but a regular function pointers with operator as first parameter + //TODO Fix panning for the Percussion channels, would any opl3 player use it and actually really change it though? + //TODO Check if having the same accuracy in all frequency multipliers sounds better or not + + //DUNNO Keyon in 4op, switch to 2op without keyoff. +*/ + +// Last synch with DOSBox SVN trunk r3556 + +#include "dbopl.h" + +#ifndef DISABLE_DOSBOX_OPL + +namespace OPL { +namespace DOSBox { + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +namespace DBOPL { + +#define OPLRATE ((double)(14318180.0 / 288.0)) +#define TREMOLO_TABLE 52 + +//Try to use most precision for frequencies +//Else try to keep different waves in synch +//#define WAVE_PRECISION 1 +#ifndef WAVE_PRECISION +//Wave bits available in the top of the 32bit range +//Original adlib uses 10.10, we use 10.22 +#define WAVE_BITS 10 +#else +//Need some extra bits at the top to have room for octaves and frequency multiplier +//We support to 8 times lower rate +//128 * 15 * 8 = 15350, 2^13.9, so need 14 bits +#define WAVE_BITS 14 +#endif +#define WAVE_SH ( 32 - WAVE_BITS ) +#define WAVE_MASK ( ( 1 << WAVE_SH ) - 1 ) + +//Use the same accuracy as the waves +#define LFO_SH ( WAVE_SH - 10 ) +//LFO is controlled by our tremolo 256 sample limit +#define LFO_MAX ( 256 << ( LFO_SH ) ) + + +//Maximum amount of attenuation bits +//Envelope goes to 511, 9 bits +#if (DBOPL_WAVE == WAVE_TABLEMUL ) +//Uses the value directly +#define ENV_BITS ( 9 ) +#else +//Add 3 bits here for more accuracy and would have to be shifted up either way +#define ENV_BITS ( 9 ) +#endif +//Limits of the envelope with those bits and when the envelope goes silent +#define ENV_MIN 0 +#define ENV_EXTRA ( ENV_BITS - 9 ) +#define ENV_MAX ( 511 << ENV_EXTRA ) +#define ENV_LIMIT ( ( 12 * 256) >> ( 3 - ENV_EXTRA ) ) +#define ENV_SILENT( _X_ ) ( (_X_) >= ENV_LIMIT ) + +//Attack/decay/release rate counter shift +#define RATE_SH 24 +#define RATE_MASK ( ( 1 << RATE_SH ) - 1 ) +//Has to fit within 16bit lookuptable +#define MUL_SH 16 + +//Check some ranges +#if ENV_EXTRA > 3 +#error Too many envelope bits +#endif + + +//How much to substract from the base value for the final attenuation +static const Bit8u KslCreateTable[16] = { + //0 will always be be lower than 7 * 8 + 64, 32, 24, 19, + 16, 12, 11, 10, + 8, 6, 5, 4, + 3, 2, 1, 0, +}; + +#define M(_X_) ((Bit8u)( (_X_) * 2)) +static const Bit8u FreqCreateTable[16] = { + M(0.5), M(1 ), M(2 ), M(3 ), M(4 ), M(5 ), M(6 ), M(7 ), + M(8 ), M(9 ), M(10), M(10), M(12), M(12), M(15), M(15) +}; +#undef M + +//We're not including the highest attack rate, that gets a special value +static const Bit8u AttackSamplesTable[13] = { + 69, 55, 46, 40, + 35, 29, 23, 20, + 19, 15, 11, 10, + 9 +}; +//On a real opl these values take 8 samples to reach and are based upon larger tables +static const Bit8u EnvelopeIncreaseTable[13] = { + 4, 5, 6, 7, + 8, 10, 12, 14, + 16, 20, 24, 28, + 32, +}; + +#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) +static Bit16u ExpTable[ 256 ]; +#endif + +#if ( DBOPL_WAVE == WAVE_HANDLER ) +//PI table used by WAVEHANDLER +static Bit16u SinTable[ 512 ]; +#endif + +#if ( DBOPL_WAVE > WAVE_HANDLER ) +//Layout of the waveform table in 512 entry intervals +//With overlapping waves we reduce the table to half it's size + +// | |//\\|____|WAV7|//__|/\ |____|/\/\| +// |\\//| | |WAV7| | \/| | | +// |06 |0126|17 |7 |3 |4 |4 5 |5 | + +//6 is just 0 shifted and masked + +static Bit16s WaveTable[ 8 * 512 ]; +//Distance into WaveTable the wave starts +static const Bit16u WaveBaseTable[8] = { + 0x000, 0x200, 0x200, 0x800, + 0xa00, 0xc00, 0x100, 0x400, + +}; +//Mask the counter with this +static const Bit16u WaveMaskTable[8] = { + 1023, 1023, 511, 511, + 1023, 1023, 512, 1023, +}; + +//Where to start the counter on at keyon +static const Bit16u WaveStartTable[8] = { + 512, 0, 0, 0, + 0, 512, 512, 256, +}; +#endif + +#if ( DBOPL_WAVE == WAVE_TABLEMUL ) +static Bit16u MulTable[ 384 ]; +#endif + +static Bit8u KslTable[ 8 * 16 ]; +static Bit8u TremoloTable[ TREMOLO_TABLE ]; +//Start of a channel behind the chip struct start +static Bit16u ChanOffsetTable[32]; +//Start of an operator behind the chip struct start +static Bit16u OpOffsetTable[64]; + +//The lower bits are the shift of the operator vibrato value +//The highest bit is right shifted to generate -1 or 0 for negation +//So taking the highest input value of 7 this gives 3, 7, 3, 0, -3, -7, -3, 0 +static const Bit8s VibratoTable[ 8 ] = { + 1 - 0x00, 0 - 0x00, 1 - 0x00, 30 - 0x00, + 1 - 0x80, 0 - 0x80, 1 - 0x80, 30 - 0x80 +}; + +//Shift strength for the ksl value determined by ksl strength +static const Bit8u KslShiftTable[4] = { + 31,1,2,0 +}; + +//Generate a table index and table shift value using input value from a selected rate +static void EnvelopeSelect( Bit8u val, Bit8u& index, Bit8u& shift ) { + if ( val < 13 * 4 ) { //Rate 0 - 12 + shift = 12 - ( val >> 2 ); + index = val & 3; + } else if ( val < 15 * 4 ) { //rate 13 - 14 + shift = 0; + index = val - 12 * 4; + } else { //rate 15 and up + shift = 0; + index = 12; + } +} + +#if ( DBOPL_WAVE == WAVE_HANDLER ) +/* + Generate the different waveforms out of the sine/exponetial table using handlers +*/ +static inline Bits MakeVolume( Bitu wave, Bitu volume ) { + Bitu total = wave + volume; + Bitu index = total & 0xff; + Bitu sig = ExpTable[ index ]; + Bitu exp = total >> 8; +#if 0 + //Check if we overflow the 31 shift limit + if ( exp >= 32 ) { + LOG_MSG( "WTF %d %d", total, exp ); + } +#endif + return (sig >> exp); +} + +static Bits DB_FASTCALL WaveForm0( Bitu i, Bitu volume ) { + Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 + Bitu wave = SinTable[i & 511]; + return (MakeVolume( wave, volume ) ^ neg) - neg; +} +static Bits DB_FASTCALL WaveForm1( Bitu i, Bitu volume ) { + Bit32u wave = SinTable[i & 511]; + wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm2( Bitu i, Bitu volume ) { + Bitu wave = SinTable[i & 511]; + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm3( Bitu i, Bitu volume ) { + Bitu wave = SinTable[i & 255]; + wave |= ( ( (i ^ 256 ) & 256) - 1) >> ( 32 - 12 ); + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm4( Bitu i, Bitu volume ) { + //Twice as fast + i <<= 1; + Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 + Bitu wave = SinTable[i & 511]; + wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); + return (MakeVolume( wave, volume ) ^ neg) - neg; +} +static Bits DB_FASTCALL WaveForm5( Bitu i, Bitu volume ) { + //Twice as fast + i <<= 1; + Bitu wave = SinTable[i & 511]; + wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm6( Bitu i, Bitu volume ) { + Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 + return (MakeVolume( 0, volume ) ^ neg) - neg; +} +static Bits DB_FASTCALL WaveForm7( Bitu i, Bitu volume ) { + //Negative is reversed here + Bits neg = (( i >> 9) & 1) - 1; + Bitu wave = (i << 3); + //When negative the volume also runs backwards + wave = ((wave ^ neg) - neg) & 4095; + return (MakeVolume( wave, volume ) ^ neg) - neg; +} + +static const WaveHandler WaveHandlerTable[8] = { + WaveForm0, WaveForm1, WaveForm2, WaveForm3, + WaveForm4, WaveForm5, WaveForm6, WaveForm7 +}; + +#endif + +/* + Operator +*/ + +//We zero out when rate == 0 +inline void Operator::UpdateAttack( const Chip* chip ) { + Bit8u rate = reg60 >> 4; + if ( rate ) { + Bit8u val = (rate << 2) + ksr; + attackAdd = chip->attackRates[ val ]; + rateZero &= ~(1 << ATTACK); + } else { + attackAdd = 0; + rateZero |= (1 << ATTACK); + } +} +inline void Operator::UpdateDecay( const Chip* chip ) { + Bit8u rate = reg60 & 0xf; + if ( rate ) { + Bit8u val = (rate << 2) + ksr; + decayAdd = chip->linearRates[ val ]; + rateZero &= ~(1 << DECAY); + } else { + decayAdd = 0; + rateZero |= (1 << DECAY); + } +} +inline void Operator::UpdateRelease( const Chip* chip ) { + Bit8u rate = reg80 & 0xf; + if ( rate ) { + Bit8u val = (rate << 2) + ksr; + releaseAdd = chip->linearRates[ val ]; + rateZero &= ~(1 << RELEASE); + if ( !(reg20 & MASK_SUSTAIN ) ) { + rateZero &= ~( 1 << SUSTAIN ); + } + } else { + rateZero |= (1 << RELEASE); + releaseAdd = 0; + if ( !(reg20 & MASK_SUSTAIN ) ) { + rateZero |= ( 1 << SUSTAIN ); + } + } +} + +inline void Operator::UpdateAttenuation( ) { + Bit8u kslBase = (Bit8u)((chanData >> SHIFT_KSLBASE) & 0xff); + Bit32u tl = reg40 & 0x3f; + Bit8u kslShift = KslShiftTable[ reg40 >> 6 ]; + //Make sure the attenuation goes to the right bits + totalLevel = tl << ( ENV_BITS - 7 ); //Total level goes 2 bits below max + totalLevel += ( kslBase << ENV_EXTRA ) >> kslShift; +} + +void Operator::UpdateFrequency( ) { + Bit32u freq = chanData & (( 1 << 10 ) - 1); + Bit32u block = (chanData >> 10) & 0xff; +#ifdef WAVE_PRECISION + block = 7 - block; + waveAdd = ( freq * freqMul ) >> block; +#else + waveAdd = ( freq << block ) * freqMul; +#endif + if ( reg20 & MASK_VIBRATO ) { + vibStrength = (Bit8u)(freq >> 7); + +#ifdef WAVE_PRECISION + vibrato = ( vibStrength * freqMul ) >> block; +#else + vibrato = ( vibStrength << block ) * freqMul; +#endif + } else { + vibStrength = 0; + vibrato = 0; + } +} + +void Operator::UpdateRates( const Chip* chip ) { + //Mame seems to reverse this where enabling ksr actually lowers + //the rate, but pdf manuals says otherwise? + Bit8u newKsr = (Bit8u)((chanData >> SHIFT_KEYCODE) & 0xff); + if ( !( reg20 & MASK_KSR ) ) { + newKsr >>= 2; + } + if ( ksr == newKsr ) + return; + ksr = newKsr; + UpdateAttack( chip ); + UpdateDecay( chip ); + UpdateRelease( chip ); +} + +INLINE Bit32s Operator::RateForward( Bit32u add ) { + rateIndex += add; + Bit32s ret = rateIndex >> RATE_SH; + rateIndex = rateIndex & RATE_MASK; + return ret; +} + +template< Operator::State yes> +Bits Operator::TemplateVolume( ) { + Bit32s vol = volume; + Bit32s change; + switch ( yes ) { + case OFF: + return ENV_MAX; + case ATTACK: + change = RateForward( attackAdd ); + if ( !change ) + return vol; + vol += ( (~vol) * change ) >> 3; + if ( vol < ENV_MIN ) { + volume = ENV_MIN; + rateIndex = 0; + SetState( DECAY ); + return ENV_MIN; + } + break; + case DECAY: + vol += RateForward( decayAdd ); + if ( GCC_UNLIKELY(vol >= sustainLevel) ) { + //Check if we didn't overshoot max attenuation, then just go off + if ( GCC_UNLIKELY(vol >= ENV_MAX) ) { + volume = ENV_MAX; + SetState( OFF ); + return ENV_MAX; + } + //Continue as sustain + rateIndex = 0; + SetState( SUSTAIN ); + } + break; + case SUSTAIN: + if ( reg20 & MASK_SUSTAIN ) { + return vol; + } + //In sustain phase, but not sustaining, do regular release + case RELEASE: + vol += RateForward( releaseAdd ); + if ( GCC_UNLIKELY(vol >= ENV_MAX) ) { + volume = ENV_MAX; + SetState( OFF ); + return ENV_MAX; + } + break; + } + volume = vol; + return vol; +} + +static const VolumeHandler VolumeHandlerTable[5] = { + &Operator::TemplateVolume< Operator::OFF >, + &Operator::TemplateVolume< Operator::RELEASE >, + &Operator::TemplateVolume< Operator::SUSTAIN >, + &Operator::TemplateVolume< Operator::DECAY >, + &Operator::TemplateVolume< Operator::ATTACK > +}; + +INLINE Bitu Operator::ForwardVolume() { + return currentLevel + (this->*volHandler)(); +} + + +INLINE Bitu Operator::ForwardWave() { + waveIndex += waveCurrent; + return waveIndex >> WAVE_SH; +} + +void Operator::Write20( const Chip* chip, Bit8u val ) { + Bit8u change = (reg20 ^ val ); + if ( !change ) + return; + reg20 = val; + //Shift the tremolo bit over the entire register, saved a branch, YES! + tremoloMask = (Bit8s)(val) >> 7; + tremoloMask &= ~(( 1 << ENV_EXTRA ) -1); + //Update specific features based on changes + if ( change & MASK_KSR ) { + UpdateRates( chip ); + } + //With sustain enable the volume doesn't change + if ( reg20 & MASK_SUSTAIN || ( !releaseAdd ) ) { + rateZero |= ( 1 << SUSTAIN ); + } else { + rateZero &= ~( 1 << SUSTAIN ); + } + //Frequency multiplier or vibrato changed + if ( change & (0xf | MASK_VIBRATO) ) { + freqMul = chip->freqMul[ val & 0xf ]; + UpdateFrequency(); + } +} + +void Operator::Write40( const Chip* /*chip*/, Bit8u val ) { + if (!(reg40 ^ val )) + return; + reg40 = val; + UpdateAttenuation( ); +} + +void Operator::Write60( const Chip* chip, Bit8u val ) { + Bit8u change = reg60 ^ val; + reg60 = val; + if ( change & 0x0f ) { + UpdateDecay( chip ); + } + if ( change & 0xf0 ) { + UpdateAttack( chip ); + } +} + +void Operator::Write80( const Chip* chip, Bit8u val ) { + Bit8u change = (reg80 ^ val ); + if ( !change ) + return; + reg80 = val; + Bit8u sustain = val >> 4; + //Turn 0xf into 0x1f + sustain |= ( sustain + 1) & 0x10; + sustainLevel = sustain << ( ENV_BITS - 5 ); + if ( change & 0x0f ) { + UpdateRelease( chip ); + } +} + +void Operator::WriteE0( const Chip* chip, Bit8u val ) { + if ( !(regE0 ^ val) ) + return; + //in opl3 mode you can always selet 7 waveforms regardless of waveformselect + Bit8u waveForm = val & ( ( 0x3 & chip->waveFormMask ) | (0x7 & chip->opl3Active ) ); + regE0 = val; +#if ( DBOPL_WAVE == WAVE_HANDLER ) + waveHandler = WaveHandlerTable[ waveForm ]; +#else + waveBase = WaveTable + WaveBaseTable[ waveForm ]; + waveStart = WaveStartTable[ waveForm ] << WAVE_SH; + waveMask = WaveMaskTable[ waveForm ]; +#endif +} + +INLINE void Operator::SetState( Bit8u s ) { + state = s; + volHandler = VolumeHandlerTable[ s ]; +} + +INLINE bool Operator::Silent() const { + if ( !ENV_SILENT( totalLevel + volume ) ) + return false; + if ( !(rateZero & ( 1 << state ) ) ) + return false; + return true; +} + +INLINE void Operator::Prepare( const Chip* chip ) { + currentLevel = totalLevel + (chip->tremoloValue & tremoloMask); + waveCurrent = waveAdd; + if ( vibStrength >> chip->vibratoShift ) { + Bit32s add = vibrato >> chip->vibratoShift; + //Sign extend over the shift value + Bit32s neg = chip->vibratoSign; + //Negate the add with -1 or 0 + add = ( add ^ neg ) - neg; + waveCurrent += add; + } +} + +void Operator::KeyOn( Bit8u mask ) { + if ( !keyOn ) { + //Restart the frequency generator +#if ( DBOPL_WAVE > WAVE_HANDLER ) + waveIndex = waveStart; +#else + waveIndex = 0; +#endif + rateIndex = 0; + SetState( ATTACK ); + } + keyOn |= mask; +} + +void Operator::KeyOff( Bit8u mask ) { + keyOn &= ~mask; + if ( !keyOn ) { + if ( state != OFF ) { + SetState( RELEASE ); + } + } +} + +INLINE Bits Operator::GetWave( Bitu index, Bitu vol ) { +#if ( DBOPL_WAVE == WAVE_HANDLER ) + return waveHandler( index, vol << ( 3 - ENV_EXTRA ) ); +#elif ( DBOPL_WAVE == WAVE_TABLEMUL ) + return (waveBase[ index & waveMask ] * MulTable[ vol >> ENV_EXTRA ]) >> MUL_SH; +#elif ( DBOPL_WAVE == WAVE_TABLELOG ) + Bit32s wave = waveBase[ index & waveMask ]; + Bit32u total = ( wave & 0x7fff ) + ( vol << ( 3 - ENV_EXTRA ) ); + Bit32s sig = ExpTable[ total & 0xff ]; + Bit32u exp = total >> 8; + Bit32s neg = wave >> 16; + return ((sig ^ neg) - neg) >> exp; +#else +#error "No valid wave routine" +#endif +} + +INLINE Bits Operator::GetSample( Bits modulation ) { + Bitu vol = ForwardVolume(); + if ( ENV_SILENT( vol ) ) { + //Simply forward the wave + waveIndex += waveCurrent; + return 0; + } else { + Bitu index = ForwardWave(); + index += modulation; + return GetWave( index, vol ); + } +} + +Operator::Operator() { + chanData = 0; + freqMul = 0; + waveIndex = 0; + waveAdd = 0; + waveCurrent = 0; + keyOn = 0; + ksr = 0; + reg20 = 0; + reg40 = 0; + reg60 = 0; + reg80 = 0; + regE0 = 0; + SetState( OFF ); + rateZero = (1 << OFF); + sustainLevel = ENV_MAX; + currentLevel = ENV_MAX; + totalLevel = ENV_MAX; + volume = ENV_MAX; + releaseAdd = 0; +} + +/* + Channel +*/ + +Channel::Channel() { + old[0] = old[1] = 0; + chanData = 0; + regB0 = 0; + regC0 = 0; + maskLeft = -1; + maskRight = -1; + feedback = 31; + fourMask = 0; + synthHandler = &Channel::BlockTemplate< sm2FM >; +} + +void Channel::SetChanData( const Chip* chip, Bit32u data ) { + Bit32u change = chanData ^ data; + chanData = data; + Op( 0 )->chanData = data; + Op( 1 )->chanData = data; + //Since a frequency update triggered this, always update frequency + Op( 0 )->UpdateFrequency(); + Op( 1 )->UpdateFrequency(); + if ( change & ( 0xff << SHIFT_KSLBASE ) ) { + Op( 0 )->UpdateAttenuation(); + Op( 1 )->UpdateAttenuation(); + } + if ( change & ( 0xff << SHIFT_KEYCODE ) ) { + Op( 0 )->UpdateRates( chip ); + Op( 1 )->UpdateRates( chip ); + } +} + +void Channel::UpdateFrequency( const Chip* chip, Bit8u fourOp ) { + //Extrace the frequency bits + Bit32u data = chanData & 0xffff; + Bit32u kslBase = KslTable[ data >> 6 ]; + Bit32u keyCode = ( data & 0x1c00) >> 9; + if ( chip->reg08 & 0x40 ) { + keyCode |= ( data & 0x100)>>8; /* notesel == 1 */ + } else { + keyCode |= ( data & 0x200)>>9; /* notesel == 0 */ + } + //Add the keycode and ksl into the highest bits of chanData + data |= (keyCode << SHIFT_KEYCODE) | ( kslBase << SHIFT_KSLBASE ); + ( this + 0 )->SetChanData( chip, data ); + if ( fourOp & 0x3f ) { + ( this + 1 )->SetChanData( chip, data ); + } +} + +void Channel::WriteA0( const Chip* chip, Bit8u val ) { + Bit8u fourOp = chip->reg104 & chip->opl3Active & fourMask; + //Don't handle writes to silent fourop channels + if ( fourOp > 0x80 ) + return; + Bit32u change = (chanData ^ val ) & 0xff; + if ( change ) { + chanData ^= change; + UpdateFrequency( chip, fourOp ); + } +} + +void Channel::WriteB0( const Chip* chip, Bit8u val ) { + Bit8u fourOp = chip->reg104 & chip->opl3Active & fourMask; + //Don't handle writes to silent fourop channels + if ( fourOp > 0x80 ) + return; + Bitu change = (chanData ^ ( val << 8 ) ) & 0x1f00; + if ( change ) { + chanData ^= change; + UpdateFrequency( chip, fourOp ); + } + //Check for a change in the keyon/off state + if ( !(( val ^ regB0) & 0x20)) + return; + regB0 = val; + if ( val & 0x20 ) { + Op(0)->KeyOn( 0x1 ); + Op(1)->KeyOn( 0x1 ); + if ( fourOp & 0x3f ) { + ( this + 1 )->Op(0)->KeyOn( 1 ); + ( this + 1 )->Op(1)->KeyOn( 1 ); + } + } else { + Op(0)->KeyOff( 0x1 ); + Op(1)->KeyOff( 0x1 ); + if ( fourOp & 0x3f ) { + ( this + 1 )->Op(0)->KeyOff( 1 ); + ( this + 1 )->Op(1)->KeyOff( 1 ); + } + } +} + +void Channel::WriteC0( const Chip* chip, Bit8u val ) { + Bit8u change = val ^ regC0; + if ( !change ) + return; + regC0 = val; + feedback = ( val >> 1 ) & 7; + if ( feedback ) { + //We shift the input to the right 10 bit wave index value + feedback = 9 - feedback; + } else { + feedback = 31; + } + //Select the new synth mode + if ( chip->opl3Active ) { + //4-op mode enabled for this channel + if ( (chip->reg104 & fourMask) & 0x3f ) { + Channel* chan0, *chan1; + //Check if it's the 2nd channel in a 4-op + if ( !(fourMask & 0x80 ) ) { + chan0 = this; + chan1 = this + 1; + } else { + chan0 = this - 1; + chan1 = this; + } + + Bit8u synth = ( (chan0->regC0 & 1) << 0 )| (( chan1->regC0 & 1) << 1 ); + switch ( synth ) { + case 0: + chan0->synthHandler = &Channel::BlockTemplate< sm3FMFM >; + break; + case 1: + chan0->synthHandler = &Channel::BlockTemplate< sm3AMFM >; + break; + case 2: + chan0->synthHandler = &Channel::BlockTemplate< sm3FMAM >; + break; + case 3: + chan0->synthHandler = &Channel::BlockTemplate< sm3AMAM >; + break; + } + //Disable updating percussion channels + } else if ((fourMask & 0x40) && ( chip->regBD & 0x20) ) { + + //Regular dual op, am or fm + } else if ( val & 1 ) { + synthHandler = &Channel::BlockTemplate< sm3AM >; + } else { + synthHandler = &Channel::BlockTemplate< sm3FM >; + } + maskLeft = ( val & 0x10 ) ? -1 : 0; + maskRight = ( val & 0x20 ) ? -1 : 0; + //opl2 active + } else { + //Disable updating percussion channels + if ( (fourMask & 0x40) && ( chip->regBD & 0x20 ) ) { + + //Regular dual op, am or fm + } else if ( val & 1 ) { + synthHandler = &Channel::BlockTemplate< sm2AM >; + } else { + synthHandler = &Channel::BlockTemplate< sm2FM >; + } + } +} + +void Channel::ResetC0( const Chip* chip ) { + Bit8u val = regC0; + regC0 ^= 0xff; + WriteC0( chip, val ); +} + +template< bool opl3Mode> +INLINE void Channel::GeneratePercussion( Chip* chip, Bit32s* output ) { + Channel* chan = this; + + //BassDrum + Bit32s mod = (Bit32u)((old[0] + old[1])) >> feedback; + old[0] = old[1]; + old[1] = Op(0)->GetSample( mod ); + + //When bassdrum is in AM mode first operator is ignoed + if ( chan->regC0 & 1 ) { + mod = 0; + } else { + mod = old[0]; + } + Bit32s sample = Op(1)->GetSample( mod ); + + + //Precalculate stuff used by other outputs + Bit32u noiseBit = chip->ForwardNoise() & 0x1; + Bit32u c2 = Op(2)->ForwardWave(); + Bit32u c5 = Op(5)->ForwardWave(); + Bit32u phaseBit = (((c2 & 0x88) ^ ((c2<<5) & 0x80)) | ((c5 ^ (c5<<2)) & 0x20)) ? 0x02 : 0x00; + + //Hi-Hat + Bit32u hhVol = Op(2)->ForwardVolume(); + if ( !ENV_SILENT( hhVol ) ) { + Bit32u hhIndex = (phaseBit<<8) | (0x34 << ( phaseBit ^ (noiseBit << 1 ))); + sample += Op(2)->GetWave( hhIndex, hhVol ); + } + //Snare Drum + Bit32u sdVol = Op(3)->ForwardVolume(); + if ( !ENV_SILENT( sdVol ) ) { + Bit32u sdIndex = ( 0x100 + (c2 & 0x100) ) ^ ( noiseBit << 8 ); + sample += Op(3)->GetWave( sdIndex, sdVol ); + } + //Tom-tom + sample += Op(4)->GetSample( 0 ); + + //Top-Cymbal + Bit32u tcVol = Op(5)->ForwardVolume(); + if ( !ENV_SILENT( tcVol ) ) { + Bit32u tcIndex = (1 + phaseBit) << 8; + sample += Op(5)->GetWave( tcIndex, tcVol ); + } + sample <<= 1; + if ( opl3Mode ) { + output[0] += sample; + output[1] += sample; + } else { + output[0] += sample; + } +} + +template +Channel* Channel::BlockTemplate( Chip* chip, Bit32u samples, Bit32s* output ) { + switch( mode ) { + case sm2AM: + case sm3AM: + if ( Op(0)->Silent() && Op(1)->Silent() ) { + old[0] = old[1] = 0; + return (this + 1); + } + break; + case sm2FM: + case sm3FM: + if ( Op(1)->Silent() ) { + old[0] = old[1] = 0; + return (this + 1); + } + break; + case sm3FMFM: + if ( Op(3)->Silent() ) { + old[0] = old[1] = 0; + return (this + 2); + } + break; + case sm3AMFM: + if ( Op(0)->Silent() && Op(3)->Silent() ) { + old[0] = old[1] = 0; + return (this + 2); + } + break; + case sm3FMAM: + if ( Op(1)->Silent() && Op(3)->Silent() ) { + old[0] = old[1] = 0; + return (this + 2); + } + break; + case sm3AMAM: + if ( Op(0)->Silent() && Op(2)->Silent() && Op(3)->Silent() ) { + old[0] = old[1] = 0; + return (this + 2); + } + break; + case sm2Percussion: + // This case was not handled in the DOSBox code either + // thus we leave this blank. + // TODO: Consider checking this. + break; + case sm3Percussion: + // This case was not handled in the DOSBox code either + // thus we leave this blank. + // TODO: Consider checking this. + break; + case sm4Start: + // This case was not handled in the DOSBox code either + // thus we leave this blank. + // TODO: Consider checking this. + break; + case sm6Start: + // This case was not handled in the DOSBox code either + // thus we leave this blank. + // TODO: Consider checking this. + break; + } + //Init the operators with the the current vibrato and tremolo values + Op( 0 )->Prepare( chip ); + Op( 1 )->Prepare( chip ); + if ( mode > sm4Start ) { + Op( 2 )->Prepare( chip ); + Op( 3 )->Prepare( chip ); + } + if ( mode > sm6Start ) { + Op( 4 )->Prepare( chip ); + Op( 5 )->Prepare( chip ); + } + for ( Bitu i = 0; i < samples; i++ ) { + //Early out for percussion handlers + if ( mode == sm2Percussion ) { + GeneratePercussion( chip, output + i ); + continue; //Prevent some unitialized value bitching + } else if ( mode == sm3Percussion ) { + GeneratePercussion( chip, output + i * 2 ); + continue; //Prevent some unitialized value bitching + } + + //Do unsigned shift so we can shift out all bits but still stay in 10 bit range otherwise + Bit32s mod = (Bit32u)((old[0] + old[1])) >> feedback; + old[0] = old[1]; + old[1] = Op(0)->GetSample( mod ); + Bit32s sample; + Bit32s out0 = old[0]; + if ( mode == sm2AM || mode == sm3AM ) { + sample = out0 + Op(1)->GetSample( 0 ); + } else if ( mode == sm2FM || mode == sm3FM ) { + sample = Op(1)->GetSample( out0 ); + } else if ( mode == sm3FMFM ) { + Bits next = Op(1)->GetSample( out0 ); + next = Op(2)->GetSample( next ); + sample = Op(3)->GetSample( next ); + } else if ( mode == sm3AMFM ) { + sample = out0; + Bits next = Op(1)->GetSample( 0 ); + next = Op(2)->GetSample( next ); + sample += Op(3)->GetSample( next ); + } else if ( mode == sm3FMAM ) { + sample = Op(1)->GetSample( out0 ); + Bits next = Op(2)->GetSample( 0 ); + sample += Op(3)->GetSample( next ); + } else if ( mode == sm3AMAM ) { + sample = out0; + Bits next = Op(1)->GetSample( 0 ); + sample += Op(2)->GetSample( next ); + sample += Op(3)->GetSample( 0 ); + } + switch( mode ) { + case sm2AM: + case sm2FM: + output[ i ] += sample; + break; + case sm3AM: + case sm3FM: + case sm3FMFM: + case sm3AMFM: + case sm3FMAM: + case sm3AMAM: + output[ i * 2 + 0 ] += sample & maskLeft; + output[ i * 2 + 1 ] += sample & maskRight; + break; + case sm2Percussion: + // This case was not handled in the DOSBox code either + // thus we leave this blank. + // TODO: Consider checking this. + break; + case sm3Percussion: + // This case was not handled in the DOSBox code either + // thus we leave this blank. + // TODO: Consider checking this. + break; + case sm4Start: + // This case was not handled in the DOSBox code either + // thus we leave this blank. + // TODO: Consider checking this. + break; + case sm6Start: + // This case was not handled in the DOSBox code either + // thus we leave this blank. + // TODO: Consider checking this. + break; + } + } + switch( mode ) { + case sm2AM: + case sm2FM: + case sm3AM: + case sm3FM: + return ( this + 1 ); + case sm3FMFM: + case sm3AMFM: + case sm3FMAM: + case sm3AMAM: + return( this + 2 ); + case sm2Percussion: + case sm3Percussion: + return( this + 3 ); + case sm4Start: + // This case was not handled in the DOSBox code either + // thus we leave this blank. + // TODO: Consider checking this. + break; + case sm6Start: + // This case was not handled in the DOSBox code either + // thus we leave this blank. + // TODO: Consider checking this. + break; + } + return 0; +} + +/* + Chip +*/ + +Chip::Chip() { + reg08 = 0; + reg04 = 0; + regBD = 0; + reg104 = 0; + opl3Active = 0; +} + +INLINE Bit32u Chip::ForwardNoise() { + noiseCounter += noiseAdd; + Bitu count = noiseCounter >> LFO_SH; + noiseCounter &= WAVE_MASK; + for ( ; count > 0; --count ) { + //Noise calculation from mame + noiseValue ^= ( 0x800302 ) & ( 0 - (noiseValue & 1 ) ); + noiseValue >>= 1; + } + return noiseValue; +} + +INLINE Bit32u Chip::ForwardLFO( Bit32u samples ) { + //Current vibrato value, runs 4x slower than tremolo + vibratoSign = ( VibratoTable[ vibratoIndex >> 2] ) >> 7; + vibratoShift = ( VibratoTable[ vibratoIndex >> 2] & 7) + vibratoStrength; + tremoloValue = TremoloTable[ tremoloIndex ] >> tremoloStrength; + + //Check hom many samples there can be done before the value changes + Bit32u todo = LFO_MAX - lfoCounter; + Bit32u count = (todo + lfoAdd - 1) / lfoAdd; + if ( count > samples ) { + count = samples; + lfoCounter += count * lfoAdd; + } else { + lfoCounter += count * lfoAdd; + lfoCounter &= (LFO_MAX - 1); + //Maximum of 7 vibrato value * 4 + vibratoIndex = ( vibratoIndex + 1 ) & 31; + //Clip tremolo to the the table size + if ( tremoloIndex + 1 < TREMOLO_TABLE ) + ++tremoloIndex; + else + tremoloIndex = 0; + } + return count; +} + + +void Chip::WriteBD( Bit8u val ) { + Bit8u change = regBD ^ val; + if ( !change ) + return; + regBD = val; + //TODO could do this with shift and xor? + vibratoStrength = (val & 0x40) ? 0x00 : 0x01; + tremoloStrength = (val & 0x80) ? 0x00 : 0x02; + if ( val & 0x20 ) { + //Drum was just enabled, make sure channel 6 has the right synth + if ( change & 0x20 ) { + if ( opl3Active ) { + chan[6].synthHandler = &Channel::BlockTemplate< sm3Percussion >; + } else { + chan[6].synthHandler = &Channel::BlockTemplate< sm2Percussion >; + } + } + //Bass Drum + if ( val & 0x10 ) { + chan[6].op[0].KeyOn( 0x2 ); + chan[6].op[1].KeyOn( 0x2 ); + } else { + chan[6].op[0].KeyOff( 0x2 ); + chan[6].op[1].KeyOff( 0x2 ); + } + //Hi-Hat + if ( val & 0x1 ) { + chan[7].op[0].KeyOn( 0x2 ); + } else { + chan[7].op[0].KeyOff( 0x2 ); + } + //Snare + if ( val & 0x8 ) { + chan[7].op[1].KeyOn( 0x2 ); + } else { + chan[7].op[1].KeyOff( 0x2 ); + } + //Tom-Tom + if ( val & 0x4 ) { + chan[8].op[0].KeyOn( 0x2 ); + } else { + chan[8].op[0].KeyOff( 0x2 ); + } + //Top Cymbal + if ( val & 0x2 ) { + chan[8].op[1].KeyOn( 0x2 ); + } else { + chan[8].op[1].KeyOff( 0x2 ); + } + //Toggle keyoffs when we turn off the percussion + } else if ( change & 0x20 ) { + //Trigger a reset to setup the original synth handler + chan[6].ResetC0( this ); + chan[6].op[0].KeyOff( 0x2 ); + chan[6].op[1].KeyOff( 0x2 ); + chan[7].op[0].KeyOff( 0x2 ); + chan[7].op[1].KeyOff( 0x2 ); + chan[8].op[0].KeyOff( 0x2 ); + chan[8].op[1].KeyOff( 0x2 ); + } +} + + +#define REGOP( _FUNC_ ) \ + index = ( ( reg >> 3) & 0x20 ) | ( reg & 0x1f ); \ + if ( OpOffsetTable[ index ] ) { \ + Operator* regOp = (Operator*)( ((char *)this ) + OpOffsetTable[ index ] ); \ + regOp->_FUNC_( this, val ); \ + } + +#define REGCHAN( _FUNC_ ) \ + index = ( ( reg >> 4) & 0x10 ) | ( reg & 0xf ); \ + if ( ChanOffsetTable[ index ] ) { \ + Channel* regChan = (Channel*)( ((char *)this ) + ChanOffsetTable[ index ] ); \ + regChan->_FUNC_( this, val ); \ + } + +void Chip::WriteReg( Bit32u reg, Bit8u val ) { + Bitu index; + switch ( (reg & 0xf0) >> 4 ) { + case 0x00 >> 4: + if ( reg == 0x01 ) { + waveFormMask = ( val & 0x20 ) ? 0x7 : 0x0; + } else if ( reg == 0x104 ) { + //Only detect changes in lowest 6 bits + if ( !((reg104 ^ val) & 0x3f) ) + return; + //Always keep the highest bit enabled, for checking > 0x80 + reg104 = 0x80 | ( val & 0x3f ); + } else if ( reg == 0x105 ) { + //MAME says the real opl3 doesn't reset anything on opl3 disable/enable till the next write in another register + if ( !((opl3Active ^ val) & 1 ) ) + return; + opl3Active = ( val & 1 ) ? 0xff : 0; + //Update the 0xc0 register for all channels to signal the switch to mono/stereo handlers + for ( int i = 0; i < 18;i++ ) { + chan[i].ResetC0( this ); + } + } else if ( reg == 0x08 ) { + reg08 = val; + } + case 0x10 >> 4: + break; + case 0x20 >> 4: + case 0x30 >> 4: + REGOP( Write20 ); + break; + case 0x40 >> 4: + case 0x50 >> 4: + REGOP( Write40 ); + break; + case 0x60 >> 4: + case 0x70 >> 4: + REGOP( Write60 ); + break; + case 0x80 >> 4: + case 0x90 >> 4: + REGOP( Write80 ); + break; + case 0xa0 >> 4: + REGCHAN( WriteA0 ); + break; + case 0xb0 >> 4: + if ( reg == 0xbd ) { + WriteBD( val ); + } else { + REGCHAN( WriteB0 ); + } + break; + case 0xc0 >> 4: + REGCHAN( WriteC0 ); + case 0xd0 >> 4: + break; + case 0xe0 >> 4: + case 0xf0 >> 4: + REGOP( WriteE0 ); + break; + } +} + + +Bit32u Chip::WriteAddr( Bit32u port, Bit8u val ) { + switch ( port & 3 ) { + case 0: + return val; + case 2: + if ( opl3Active || (val == 0x05) ) + return 0x100 | val; + else + return val; + } + return 0; +} + +void Chip::GenerateBlock2( Bitu total, Bit32s* output ) { + while ( total > 0 ) { + Bit32u samples = ForwardLFO( total ); + memset(output, 0, sizeof(Bit32s) * samples); + int count = 0; + for( Channel* ch = chan; ch < chan + 9; ) { + count++; + ch = (ch->*(ch->synthHandler))( this, samples, output ); + } + total -= samples; + output += samples; + } +} + +void Chip::GenerateBlock3( Bitu total, Bit32s* output ) { + while ( total > 0 ) { + Bit32u samples = ForwardLFO( total ); + memset(output, 0, sizeof(Bit32s) * 2 * samples); + int count = 0; + for( Channel* ch = chan; ch < chan + 18; ) { + count++; + ch = (ch->*(ch->synthHandler))( this, samples, output ); + } + total -= samples; + output += samples * 2; + } +} + +void Chip::Setup( Bit32u rate ) { + double scale = OPLRATE / (double)rate; + + //Noise counter is run at the same precision as general waves + noiseAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) ); + noiseCounter = 0; + noiseValue = 1; //Make sure it triggers the noise xor the first time + //The low frequency oscillation counter + //Every time his overflows vibrato and tremoloindex are increased + lfoAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) ); + lfoCounter = 0; + vibratoIndex = 0; + tremoloIndex = 0; + + //With higher octave this gets shifted up + //-1 since the freqCreateTable = *2 +#ifdef WAVE_PRECISION + double freqScale = ( 1 << 7 ) * scale * ( 1 << ( WAVE_SH - 1 - 10)); + for ( int i = 0; i < 16; i++ ) { + freqMul[i] = (Bit32u)( 0.5 + freqScale * FreqCreateTable[ i ] ); + } +#else + Bit32u freqScale = (Bit32u)( 0.5 + scale * ( 1 << ( WAVE_SH - 1 - 10))); + for ( int i = 0; i < 16; i++ ) { + freqMul[i] = freqScale * FreqCreateTable[ i ]; + } +#endif + + //-3 since the real envelope takes 8 steps to reach the single value we supply + for ( Bit8u i = 0; i < 76; i++ ) { + Bit8u index, shift; + EnvelopeSelect( i, index, shift ); + linearRates[i] = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH + ENV_EXTRA - shift - 3 ))); + } + //Generate the best matching attack rate + for ( Bit8u i = 0; i < 62; i++ ) { + Bit8u index, shift; + EnvelopeSelect( i, index, shift ); + //Original amount of samples the attack would take + Bit32s original = (Bit32u)( (AttackSamplesTable[ index ] << shift) / scale); + + Bit32s guessAdd = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH - shift - 3 ))); + Bit32s bestAdd = guessAdd; + Bit32u bestDiff = 1 << 30; + for( Bit32u passes = 0; passes < 16; passes ++ ) { + Bit32s volume = ENV_MAX; + Bit32s samples = 0; + Bit32u count = 0; + while ( volume > 0 && samples < original * 2 ) { + count += guessAdd; + Bit32s change = count >> RATE_SH; + count &= RATE_MASK; + if ( GCC_UNLIKELY(change) ) { // less than 1 % + volume += ( ~volume * change ) >> 3; + } + samples++; + + } + Bit32s diff = original - samples; + Bit32u lDiff = labs( diff ); + //Init last on first pass + if ( lDiff < bestDiff ) { + bestDiff = lDiff; + bestAdd = guessAdd; + if ( !bestDiff ) + break; + } + //Below our target + if ( diff < 0 ) { + //Better than the last time + Bit32s mul = ((original - diff) << 12) / original; + guessAdd = ((guessAdd * mul) >> 12); + guessAdd++; + } else if ( diff > 0 ) { + Bit32s mul = ((original - diff) << 12) / original; + guessAdd = (guessAdd * mul) >> 12; + guessAdd--; + } + } + attackRates[i] = bestAdd; + } + for ( Bit8u i = 62; i < 76; i++ ) { + //This should provide instant volume maximizing + attackRates[i] = 8 << RATE_SH; + } + //Setup the channels with the correct four op flags + //Channels are accessed through a table so they appear linear here + chan[ 0].fourMask = 0x00 | ( 1 << 0 ); + chan[ 1].fourMask = 0x80 | ( 1 << 0 ); + chan[ 2].fourMask = 0x00 | ( 1 << 1 ); + chan[ 3].fourMask = 0x80 | ( 1 << 1 ); + chan[ 4].fourMask = 0x00 | ( 1 << 2 ); + chan[ 5].fourMask = 0x80 | ( 1 << 2 ); + + chan[ 9].fourMask = 0x00 | ( 1 << 3 ); + chan[10].fourMask = 0x80 | ( 1 << 3 ); + chan[11].fourMask = 0x00 | ( 1 << 4 ); + chan[12].fourMask = 0x80 | ( 1 << 4 ); + chan[13].fourMask = 0x00 | ( 1 << 5 ); + chan[14].fourMask = 0x80 | ( 1 << 5 ); + + //mark the percussion channels + chan[ 6].fourMask = 0x40; + chan[ 7].fourMask = 0x40; + chan[ 8].fourMask = 0x40; + + //Clear Everything in opl3 mode + WriteReg( 0x105, 0x1 ); + for ( int i = 0; i < 512; i++ ) { + if ( i == 0x105 ) + continue; + WriteReg( i, 0xff ); + WriteReg( i, 0x0 ); + } + WriteReg( 0x105, 0x0 ); + //Clear everything in opl2 mode + for ( int i = 0; i < 255; i++ ) { + WriteReg( i, 0xff ); + WriteReg( i, 0x0 ); + } +} + +static bool doneTables = false; +void InitTables( void ) { + if ( doneTables ) + return; + doneTables = true; +#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) + //Exponential volume table, same as the real adlib + for ( int i = 0; i < 256; i++ ) { + //Save them in reverse + ExpTable[i] = (int)( 0.5 + ( pow(2.0, ( 255 - i) * ( 1.0 /256 ) )-1) * 1024 ); + ExpTable[i] += 1024; //or remove the -1 oh well :) + //Preshift to the left once so the final volume can shift to the right + ExpTable[i] *= 2; + } +#endif +#if ( DBOPL_WAVE == WAVE_HANDLER ) + //Add 0.5 for the trunc rounding of the integer cast + //Do a PI sinetable instead of the original 0.5 PI + for ( int i = 0; i < 512; i++ ) { + SinTable[i] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 ); + } +#endif +#if ( DBOPL_WAVE == WAVE_TABLEMUL ) + //Multiplication based tables + for ( int i = 0; i < 384; i++ ) { + int s = i * 8; + //TODO maybe keep some of the precision errors of the original table? + double val = ( 0.5 + ( pow(2.0, -1.0 + ( 255 - s) * ( 1.0 /256 ) )) * ( 1 << MUL_SH )); + MulTable[i] = (Bit16u)(val); + } + + //Sine Wave Base + for ( int i = 0; i < 512; i++ ) { + WaveTable[ 0x0200 + i ] = (Bit16s)(sin( (i + 0.5) * (PI / 512.0) ) * 4084); + WaveTable[ 0x0000 + i ] = -WaveTable[ 0x200 + i ]; + } + //Exponential wave + for ( int i = 0; i < 256; i++ ) { + WaveTable[ 0x700 + i ] = (Bit16s)( 0.5 + ( pow(2.0, -1.0 + ( 255 - i * 8) * ( 1.0 /256 ) ) ) * 4085 ); + WaveTable[ 0x6ff - i ] = -WaveTable[ 0x700 + i ]; + } +#endif +#if ( DBOPL_WAVE == WAVE_TABLELOG ) + //Sine Wave Base + for ( int i = 0; i < 512; i++ ) { + WaveTable[ 0x0200 + i ] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 ); + WaveTable[ 0x0000 + i ] = ((Bit16s)0x8000) | WaveTable[ 0x200 + i]; + } + //Exponential wave + for ( int i = 0; i < 256; i++ ) { + WaveTable[ 0x700 + i ] = i * 8; + WaveTable[ 0x6ff - i ] = ((Bit16s)0x8000) | i * 8; + } +#endif + + // | |//\\|____|WAV7|//__|/\ |____|/\/\| + // |\\//| | |WAV7| | \/| | | + // |06 |0126|27 |7 |3 |4 |4 5 |5 | + +#if (( DBOPL_WAVE == WAVE_TABLELOG ) || ( DBOPL_WAVE == WAVE_TABLEMUL )) + for ( int i = 0; i < 256; i++ ) { + //Fill silence gaps + WaveTable[ 0x400 + i ] = WaveTable[0]; + WaveTable[ 0x500 + i ] = WaveTable[0]; + WaveTable[ 0x900 + i ] = WaveTable[0]; + WaveTable[ 0xc00 + i ] = WaveTable[0]; + WaveTable[ 0xd00 + i ] = WaveTable[0]; + //Replicate sines in other pieces + WaveTable[ 0x800 + i ] = WaveTable[ 0x200 + i ]; + //double speed sines + WaveTable[ 0xa00 + i ] = WaveTable[ 0x200 + i * 2 ]; + WaveTable[ 0xb00 + i ] = WaveTable[ 0x000 + i * 2 ]; + WaveTable[ 0xe00 + i ] = WaveTable[ 0x200 + i * 2 ]; + WaveTable[ 0xf00 + i ] = WaveTable[ 0x200 + i * 2 ]; + } +#endif + + //Create the ksl table + for ( int oct = 0; oct < 8; oct++ ) { + int base = oct * 8; + for ( int i = 0; i < 16; i++ ) { + int val = base - KslCreateTable[i]; + if ( val < 0 ) + val = 0; + //*4 for the final range to match attenuation range + KslTable[ oct * 16 + i ] = val * 4; + } + } + //Create the Tremolo table, just increase and decrease a triangle wave + for ( Bit8u i = 0; i < TREMOLO_TABLE / 2; i++ ) { + Bit8u val = i << ENV_EXTRA; + TremoloTable[i] = val; + TremoloTable[TREMOLO_TABLE - 1 - i] = val; + } + //Create a table with offsets of the channels from the start of the chip + DBOPL::Chip* chip = 0; + for ( Bitu i = 0; i < 32; i++ ) { + Bitu index = i & 0xf; + if ( index >= 9 ) { + ChanOffsetTable[i] = 0; + continue; + } + //Make sure the four op channels follow eachother + if ( index < 6 ) { + index = (index % 3) * 2 + ( index / 3 ); + } + //Add back the bits for highest ones + if ( i >= 16 ) + index += 9; + Bitu blah = reinterpret_cast( &(chip->chan[ index ]) ); + ChanOffsetTable[i] = blah; + } + //Same for operators + for ( Bitu i = 0; i < 64; i++ ) { + if ( i % 8 >= 6 || ( (i / 8) % 4 == 3 ) ) { + OpOffsetTable[i] = 0; + continue; + } + Bitu chNum = (i / 8) * 3 + (i % 8) % 3; + //Make sure we use 16 and up for the 2nd range to match the chanoffset gap + if ( chNum >= 12 ) + chNum += 16 - 12; + Bitu opNum = ( i % 8 ) / 3; + DBOPL::Channel* chan = 0; + Bitu blah = reinterpret_cast( &(chan->op[opNum]) ); + OpOffsetTable[i] = ChanOffsetTable[ chNum ] + blah; + } +#if 0 + //Stupid checks if table's are correct + for ( Bitu i = 0; i < 18; i++ ) { + Bit32u find = (Bit16u)( &(chip->chan[ i ]) ); + for ( Bitu c = 0; c < 32; c++ ) { + if ( ChanOffsetTable[c] == find ) { + find = 0; + break; + } + } + if ( find ) { + find = find; + } + } + for ( Bitu i = 0; i < 36; i++ ) { + Bit32u find = (Bit16u)( &(chip->chan[ i / 2 ].op[i % 2]) ); + for ( Bitu c = 0; c < 64; c++ ) { + if ( OpOffsetTable[c] == find ) { + find = 0; + break; + } + } + if ( find ) { + find = find; + } + } +#endif +} + +} //Namespace DBOPL +} // End of namespace DOSBox +} // End of namespace OPL + +#endif // !DISABLE_DOSBOX_OPL diff --git a/audio/softsynth/opl/dbopl.h b/audio/softsynth/opl/dbopl.h new file mode 100644 index 0000000000..87d1045fab --- /dev/null +++ b/audio/softsynth/opl/dbopl.h @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2002-2010 The DOSBox Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Last synch with DOSBox SVN trunk r3556 + +#ifndef SOUND_SOFTSYNTH_OPL_DBOPL_H +#define SOUND_SOFTSYNTH_OPL_DBOPL_H + +#include "common/scummsys.h" + +#ifndef DISABLE_DOSBOX_OPL + +namespace OPL { +namespace DOSBox { + +//Use 8 handlers based on a small logatirmic wavetabe and an exponential table for volume +#define WAVE_HANDLER 10 +//Use a logarithmic wavetable with an exponential table for volume +#define WAVE_TABLELOG 11 +//Use a linear wavetable with a multiply table for volume +#define WAVE_TABLEMUL 12 + +//Select the type of wave generator routine +#define DBOPL_WAVE WAVE_TABLEMUL + +namespace DBOPL { + +// Type aliases for the DBOPL code +typedef int Bits; +typedef uint Bitu; + +typedef int8 Bit8s; +typedef uint8 Bit8u; + +typedef int16 Bit16s; +typedef uint16 Bit16u; + +typedef int32 Bit32s; +typedef uint32 Bit32u; + +#define DB_FASTCALL +#define GCC_UNLIKELY(x) (x) +#define INLINE inline +// ------------------------------- + +struct Chip; +struct Operator; +struct Channel; + +#if (DBOPL_WAVE == WAVE_HANDLER) +typedef Bits ( DB_FASTCALL *WaveHandler) ( Bitu i, Bitu volume ); +#endif + +typedef Bits ( DBOPL::Operator::*VolumeHandler) ( ); +typedef Channel* ( DBOPL::Channel::*SynthHandler) ( Chip* chip, Bit32u samples, Bit32s* output ); + +//Different synth modes that can generate blocks of data +typedef enum { + sm2AM, + sm2FM, + sm3AM, + sm3FM, + sm4Start, + sm3FMFM, + sm3AMFM, + sm3FMAM, + sm3AMAM, + sm6Start, + sm2Percussion, + sm3Percussion +} SynthMode; + +//Shifts for the values contained in chandata variable +enum { + SHIFT_KSLBASE = 16, + SHIFT_KEYCODE = 24 +}; + +struct Operator { +public: + //Masks for operator 20 values + enum { + MASK_KSR = 0x10, + MASK_SUSTAIN = 0x20, + MASK_VIBRATO = 0x40, + MASK_TREMOLO = 0x80 + }; + + typedef enum { + OFF, + RELEASE, + SUSTAIN, + DECAY, + ATTACK + } State; + + VolumeHandler volHandler; + +#if (DBOPL_WAVE == WAVE_HANDLER) + WaveHandler waveHandler; //Routine that generate a wave +#else + Bit16s* waveBase; + Bit32u waveMask; + Bit32u waveStart; +#endif + Bit32u waveIndex; //WAVE_BITS shifted counter of the frequency index + Bit32u waveAdd; //The base frequency without vibrato + Bit32u waveCurrent; //waveAdd + vibratao + + Bit32u chanData; //Frequency/octave and derived data coming from whatever channel controls this + Bit32u freqMul; //Scale channel frequency with this, TODO maybe remove? + Bit32u vibrato; //Scaled up vibrato strength + Bit32s sustainLevel; //When stopping at sustain level stop here + Bit32s totalLevel; //totalLevel is added to every generated volume + Bit32u currentLevel; //totalLevel + tremolo + Bit32s volume; //The currently active volume + + Bit32u attackAdd; //Timers for the different states of the envelope + Bit32u decayAdd; + Bit32u releaseAdd; + Bit32u rateIndex; //Current position of the evenlope + + Bit8u rateZero; //Bits for the different states of the envelope having no changes + Bit8u keyOn; //Bitmask of different values that can generate keyon + //Registers, also used to check for changes + Bit8u reg20, reg40, reg60, reg80, regE0; + //Active part of the envelope we're in + Bit8u state; + //0xff when tremolo is enabled + Bit8u tremoloMask; + //Strength of the vibrato + Bit8u vibStrength; + //Keep track of the calculated KSR so we can check for changes + Bit8u ksr; +private: + void SetState( Bit8u s ); + void UpdateAttack( const Chip* chip ); + void UpdateRelease( const Chip* chip ); + void UpdateDecay( const Chip* chip ); +public: + void UpdateAttenuation(); + void UpdateRates( const Chip* chip ); + void UpdateFrequency( ); + + void Write20( const Chip* chip, Bit8u val ); + void Write40( const Chip* chip, Bit8u val ); + void Write60( const Chip* chip, Bit8u val ); + void Write80( const Chip* chip, Bit8u val ); + void WriteE0( const Chip* chip, Bit8u val ); + + bool Silent() const; + void Prepare( const Chip* chip ); + + void KeyOn( Bit8u mask); + void KeyOff( Bit8u mask); + + template< State state> + Bits TemplateVolume( ); + + Bit32s RateForward( Bit32u add ); + Bitu ForwardWave(); + Bitu ForwardVolume(); + + Bits GetSample( Bits modulation ); + Bits GetWave( Bitu index, Bitu vol ); +public: + Operator(); +}; + +struct Channel { + Operator op[2]; + inline Operator* Op( Bitu index ) { + return &( ( this + (index >> 1) )->op[ index & 1 ]); + } + SynthHandler synthHandler; + Bit32u chanData; //Frequency/octave and derived values + Bit32s old[2]; //Old data for feedback + + Bit8u feedback; //Feedback shift + Bit8u regB0; //Register values to check for changes + Bit8u regC0; + //This should correspond with reg104, bit 6 indicates a Percussion channel, bit 7 indicates a silent channel + Bit8u fourMask; + Bit8s maskLeft; //Sign extended values for both channel's panning + Bit8s maskRight; + + //Forward the channel data to the operators of the channel + void SetChanData( const Chip* chip, Bit32u data ); + //Change in the chandata, check for new values and if we have to forward to operators + void UpdateFrequency( const Chip* chip, Bit8u fourOp ); + void WriteA0( const Chip* chip, Bit8u val ); + void WriteB0( const Chip* chip, Bit8u val ); + void WriteC0( const Chip* chip, Bit8u val ); + void ResetC0( const Chip* chip ); + + //call this for the first channel + template< bool opl3Mode > + void GeneratePercussion( Chip* chip, Bit32s* output ); + + //Generate blocks of data in specific modes + template + Channel* BlockTemplate( Chip* chip, Bit32u samples, Bit32s* output ); + Channel(); +}; + +struct Chip { + //This is used as the base counter for vibrato and tremolo + Bit32u lfoCounter; + Bit32u lfoAdd; + + + Bit32u noiseCounter; + Bit32u noiseAdd; + Bit32u noiseValue; + + //Frequency scales for the different multiplications + Bit32u freqMul[16]; + //Rates for decay and release for rate of this chip + Bit32u linearRates[76]; + //Best match attack rates for the rate of this chip + Bit32u attackRates[76]; + + //18 channels with 2 operators each + Channel chan[18]; + + Bit8u reg104; + Bit8u reg08; + Bit8u reg04; + Bit8u regBD; + Bit8u vibratoIndex; + Bit8u tremoloIndex; + Bit8s vibratoSign; + Bit8u vibratoShift; + Bit8u tremoloValue; + Bit8u vibratoStrength; + Bit8u tremoloStrength; + //Mask for allowed wave forms + Bit8u waveFormMask; + //0 or -1 when enabled + Bit8s opl3Active; + + //Return the maximum amount of samples before and LFO change + Bit32u ForwardLFO( Bit32u samples ); + Bit32u ForwardNoise(); + + void WriteBD( Bit8u val ); + void WriteReg(Bit32u reg, Bit8u val ); + + Bit32u WriteAddr( Bit32u port, Bit8u val ); + + void GenerateBlock2( Bitu samples, Bit32s* output ); + void GenerateBlock3( Bitu samples, Bit32s* output ); + + void Generate( Bit32u samples ); + void Setup( Bit32u r ); + + Chip(); +}; + +void InitTables(); + +} //Namespace +} // End of namespace DOSBox +} // End of namespace OPL + +#endif // !DISABLE_DOSBOX_OPL + +#endif diff --git a/audio/softsynth/opl/dosbox.cpp b/audio/softsynth/opl/dosbox.cpp new file mode 100644 index 0000000000..29993ce3d8 --- /dev/null +++ b/audio/softsynth/opl/dosbox.cpp @@ -0,0 +1,335 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +/* + * Based on AdLib emulation code of DOSBox + * Copyright (C) 2002-2009 The DOSBox Team + * Licensed under GPLv2+ + * http://www.dosbox.com + */ + +#ifndef DISABLE_DOSBOX_OPL + +#include "dosbox.h" +#include "dbopl.h" + +#include "common/system.h" +#include "common/scummsys.h" + +#include +#include + +namespace OPL { +namespace DOSBox { + +Timer::Timer() { + masked = false; + overflow = false; + enabled = false; + counter = 0; + delay = 0; +} + +void Timer::update(double time) { + if (!enabled || !delay) + return; + double deltaStart = time - startTime; + // Only set the overflow flag when not masked + if (deltaStart >= 0 && !masked) + overflow = 1; +} + +void Timer::reset(double time) { + overflow = false; + if (!delay || !enabled) + return; + double delta = (time - startTime); + double rem = fmod(delta, delay); + double next = delay - rem; + startTime = time + next; +} + +void Timer::stop() { + enabled = false; +} + +void Timer::start(double time, int scale) { + //Don't enable again + if (enabled) + return; + enabled = true; + delay = 0.001 * (256 - counter) * scale; + startTime = time + delay; +} + +bool Chip::write(uint32 reg, uint8 val) { + switch (reg) { + case 0x02: + timer[0].counter = val; + return true; + case 0x03: + timer[1].counter = val; + return true; + case 0x04: + double time = g_system->getMillis() / 1000.0; + + if (val & 0x80) { + timer[0].reset(time); + timer[1].reset(time); + } else { + timer[0].update(time); + timer[1].update(time); + + if (val & 0x1) + timer[0].start(time, 80); + else + timer[0].stop(); + + timer[0].masked = (val & 0x40) > 0; + + if (timer[0].masked) + timer[0].overflow = false; + + if (val & 0x2) + timer[1].start(time, 320); + else + timer[1].stop(); + + timer[1].masked = (val & 0x20) > 0; + + if (timer[1].masked) + timer[1].overflow = false; + } + return true; + } + return false; +} + +uint8 Chip::read() { + double time = g_system->getMillis() / 1000.0; + + timer[0].update(time); + timer[1].update(time); + + uint8 ret = 0; + // Overflow won't be set if a channel is masked + if (timer[0].overflow) { + ret |= 0x40; + ret |= 0x80; + } + if (timer[1].overflow) { + ret |= 0x20; + ret |= 0x80; + } + return ret; +} + +OPL::OPL(Config::OplType type) : _type(type), _rate(0), _emulator(0) { +} + +OPL::~OPL() { + free(); +} + +void OPL::free() { + delete _emulator; + _emulator = 0; +} + +bool OPL::init(int rate) { + free(); + + memset(&_reg, 0, sizeof(_reg)); + memset(_chip, 0, sizeof(_chip)); + + _emulator = new DBOPL::Chip(); + if (!_emulator) + return false; + + DBOPL::InitTables(); + _emulator->Setup(rate); + + if (_type == Config::kDualOpl2) { + // Setup opl3 mode in the hander + _emulator->WriteReg(0x105, 1); + } + + _rate = rate; + return true; +} + +void OPL::reset() { + init(_rate); +} + +void OPL::write(int port, int val) { + if (port&1) { + switch (_type) { + case Config::kOpl2: + case Config::kOpl3: + if (!_chip[0].write(_reg.normal, val)) + _emulator->WriteReg(_reg.normal, val); + break; + case Config::kDualOpl2: + // Not a 0x??8 port, then write to a specific port + if (!(port & 0x8)) { + byte index = (port & 2) >> 1; + dualWrite(index, _reg.dual[index], val); + } else { + //Write to both ports + dualWrite(0, _reg.dual[0], val); + dualWrite(1, _reg.dual[1], val); + } + break; + } + } else { + // Ask the handler to write the address + // Make sure to clip them in the right range + switch (_type) { + case Config::kOpl2: + _reg.normal = _emulator->WriteAddr(port, val) & 0xff; + break; + case Config::kOpl3: + _reg.normal = _emulator->WriteAddr(port, val) & 0x1ff; + break; + case Config::kDualOpl2: + // Not a 0x?88 port, when write to a specific side + if (!(port & 0x8)) { + byte index = (port & 2) >> 1; + _reg.dual[index] = val & 0xff; + } else { + _reg.dual[0] = val & 0xff; + _reg.dual[1] = val & 0xff; + } + break; + } + } +} + +byte OPL::read(int port) { + switch (_type) { + case Config::kOpl2: + if (!(port & 1)) + //Make sure the low bits are 6 on opl2 + return _chip[0].read() | 0x6; + break; + case Config::kOpl3: + if (!(port & 1)) + return _chip[0].read(); + break; + case Config::kDualOpl2: + // Only return for the lower ports + if (port & 1) + return 0xff; + // Make sure the low bits are 6 on opl2 + return _chip[(port >> 1) & 1].read() | 0x6; + } + return 0; +} + +void OPL::writeReg(int r, int v) { + byte tempReg = 0; + switch (_type) { + case Config::kOpl2: + case Config::kDualOpl2: + case Config::kOpl3: + // We can't use _handler->writeReg here directly, since it would miss timer changes. + + // Backup old setup register + tempReg = _reg.normal; + + // We need to set the register we want to write to via port 0x388 + write(0x388, r); + // Do the real writing to the register + write(0x389, v); + // Restore the old register + write(0x388, tempReg); + break; + }; +} + +void OPL::dualWrite(uint8 index, uint8 reg, uint8 val) { + // Make sure you don't use opl3 features + // Don't allow write to disable opl3 + if (reg == 5) + return; + + // Only allow 4 waveforms + if (reg >= 0xE0 && reg <= 0xE8) + val &= 3; + + // Write to the timer? + if (_chip[index].write(reg, val)) + return; + + // Enabling panning + if (reg >= 0xC0 && reg <= 0xC8) { + val &= 15; + val |= index ? 0xA0 : 0x50; + } + + uint32 fullReg = reg + (index ? 0x100 : 0); + _emulator->WriteReg(fullReg, val); +} + +void OPL::readBuffer(int16 *buffer, int length) { + // For stereo OPL cards, we divide the sample count by 2, + // to match stereo AudioStream behavior. + if (_type != Config::kOpl2) + length >>= 1; + + const uint bufferLength = 512; + int32 tempBuffer[bufferLength * 2]; + + if (_emulator->opl3Active) { + while (length > 0) { + const uint readSamples = MIN(length, bufferLength); + + _emulator->GenerateBlock3(readSamples, tempBuffer); + + for (uint i = 0; i < (readSamples << 1); ++i) + buffer[i] = tempBuffer[i]; + + buffer += (readSamples << 1); + length -= readSamples; + } + } else { + while (length > 0) { + const uint readSamples = MIN(length, bufferLength << 1); + + _emulator->GenerateBlock2(readSamples, tempBuffer); + + for (uint i = 0; i < readSamples; ++i) + buffer[i] = tempBuffer[i]; + + buffer += readSamples; + length -= readSamples; + } + } +} + +} // End of namespace DOSBox +} // End of namespace OPL + +#endif // !DISABLE_DOSBOX_ADLIB diff --git a/audio/softsynth/opl/dosbox.h b/audio/softsynth/opl/dosbox.h new file mode 100644 index 0000000000..1e92c7f7c9 --- /dev/null +++ b/audio/softsynth/opl/dosbox.h @@ -0,0 +1,110 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +/* + * Based on OPL emulation code of DOSBox + * Copyright (C) 2002-2009 The DOSBox Team + * Licensed under GPLv2+ + * http://www.dosbox.com + */ + +#ifndef SOUND_SOFTSYNTH_OPL_DOSBOX_H +#define SOUND_SOFTSYNTH_OPL_DOSBOX_H + +#ifndef DISABLE_DOSBOX_OPL + +#include "audio/fmopl.h" + +namespace OPL { +namespace DOSBox { + +struct Timer { + double startTime; + double delay; + bool enabled, overflow, masked; + uint8 counter; + + Timer(); + + //Call update before making any further changes + void update(double time); + + //On a reset make sure the start is in sync with the next cycle + void reset(double time); + + void stop(); + + void start(double time, int scale); +}; + +struct Chip { + //Last selected register + Timer timer[2]; + //Check for it being a write to the timer + bool write(uint32 addr, uint8 val); + //Read the current timer state, will use current double + uint8 read(); +}; + +namespace DBOPL { +struct Chip; +} // end of namespace DBOPL + +class OPL : public ::OPL::OPL { +private: + Config::OplType _type; + uint _rate; + + DBOPL::Chip *_emulator; + Chip _chip[2]; + union { + uint16 normal; + uint8 dual[2]; + } _reg; + + void free(); + void dualWrite(uint8 index, uint8 reg, uint8 val); +public: + OPL(Config::OplType type); + ~OPL(); + + bool init(int rate); + void reset(); + + void write(int a, int v); + byte read(int a); + + void writeReg(int r, int v); + + void readBuffer(int16 *buffer, int length); + bool isStereo() const { return _type != Config::kOpl2; } +}; + +} // End of namespace DOSBox +} // End of namespace OPL + +#endif // !DISABLE_DOSBOX_OPL + +#endif + diff --git a/audio/softsynth/opl/mame.cpp b/audio/softsynth/opl/mame.cpp new file mode 100644 index 0000000000..c875080e8f --- /dev/null +++ b/audio/softsynth/opl/mame.cpp @@ -0,0 +1,1234 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * LGPL licensed version of MAMEs fmopl (V0.37a modified) by + * Tatsuyuki Satoh. Included from LGPL'ed AdPlug. + */ + +#include +#include +#include +#include +#include + +#include "mame.h" + +#if defined (_WIN32_WCE) || defined (__SYMBIAN32__) || defined(__GP32__) || defined(GP2X) || defined (__MAEMO__) || defined(__DS__) || defined (__MINT__) || defined(__N64__) +#include "common/config-manager.h" +#endif + +#if defined(__DS__) +#include "dsmain.h" +#endif + +namespace OPL { +namespace MAME { + +OPL::~OPL() { + MAME::OPLDestroy(_opl); + _opl = 0; +} + +bool OPL::init(int rate) { + if (_opl) + MAME::OPLDestroy(_opl); + + _opl = MAME::makeAdLibOPL(rate); + return (_opl != 0); +} + +void OPL::reset() { + MAME::OPLResetChip(_opl); +} + +void OPL::write(int a, int v) { + MAME::OPLWrite(_opl, a, v); +} + +byte OPL::read(int a) { + return MAME::OPLRead(_opl, a); +} + +void OPL::writeReg(int r, int v) { + MAME::OPLWriteReg(_opl, r, v); +} + +void OPL::readBuffer(int16 *buffer, int length) { + MAME::YM3812UpdateOne(_opl, buffer, length); +} + +/* -------------------- preliminary define section --------------------- */ +/* attack/decay rate time rate */ +#define OPL_ARRATE 141280 /* RATE 4 = 2826.24ms @ 3.6MHz */ +#define OPL_DRRATE 1956000 /* RATE 4 = 39280.64ms @ 3.6MHz */ + +#define FREQ_BITS 24 /* frequency turn */ + +/* counter bits = 20 , octerve 7 */ +#define FREQ_RATE (1<<(FREQ_BITS-20)) +#define TL_BITS (FREQ_BITS+2) + +/* final output shift , limit minimum and maximum */ +#define OPL_OUTSB (TL_BITS+3-16) /* OPL output final shift 16bit */ +#define OPL_MAXOUT (0x7fff<status |= flag; + if (!(OPL->status & 0x80)) { + if (OPL->status & OPL->statusmask) { /* IRQ on */ + OPL->status |= 0x80; + /* callback user interrupt handler (IRQ is OFF to ON) */ + if (OPL->IRQHandler) + (OPL->IRQHandler)(OPL->IRQParam,1); + } + } +} + +/* status reset and IRQ handling */ +inline void OPL_STATUS_RESET(FM_OPL *OPL, int flag) { + /* reset status flag */ + OPL->status &= ~flag; + if ((OPL->status & 0x80)) { + if (!(OPL->status & OPL->statusmask)) { + OPL->status &= 0x7f; + /* callback user interrupt handler (IRQ is ON to OFF) */ + if (OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); + } + } +} + +/* IRQ mask set */ +inline void OPL_STATUSMASK_SET(FM_OPL *OPL, int flag) { + OPL->statusmask = flag; + /* IRQ handling check */ + OPL_STATUS_SET(OPL,0); + OPL_STATUS_RESET(OPL,0); +} + +/* ----- key on ----- */ +inline void OPL_KEYON(OPL_SLOT *SLOT) { + /* sin wave restart */ + SLOT->Cnt = 0; + /* set attack */ + SLOT->evm = ENV_MOD_AR; + SLOT->evs = SLOT->evsa; + SLOT->evc = EG_AST; + SLOT->eve = EG_AED; +} + +/* ----- key off ----- */ +inline void OPL_KEYOFF(OPL_SLOT *SLOT) { + if (SLOT->evm > ENV_MOD_RR) { + /* set envelope counter from envleope output */ + + // WORKAROUND: The Kyra engine does something very strange when + // starting a new song. For each channel: + // + // * The release rate is set to "fastest". + // * Any note is keyed off. + // * A very low-frequency note is keyed on. + // + // Usually, what happens next is that the real notes is keyed + // on immediately, in which case there's no problem. + // + // However, if the note is again keyed off (because the channel + // begins on a rest rather than a note), the envelope counter + // was moved from the very lowest point on the attack curve to + // the very highest point on the release curve. + // + // Again, this might not be a problem, if the release rate is + // still set to "fastest". But in many cases, it had already + // been increased. And, possibly because of inaccuracies in the + // envelope generator, that would cause the note to "fade out" + // for quite a long time. + // + // What we really need is a way to find the correct starting + // point for the envelope counter, and that may be what the + // commented-out line below is meant to do. For now, simply + // handle the pathological case. + + if (SLOT->evm == ENV_MOD_AR && SLOT->evc == EG_AST) + SLOT->evc = EG_DED; + else if (!(SLOT->evc & EG_DST)) + //SLOT->evc = (ENV_CURVE[SLOT->evc>>ENV_BITS]<evc = EG_DST; + SLOT->eve = EG_DED; + SLOT->evs = SLOT->evsr; + SLOT->evm = ENV_MOD_RR; + } +} + +/* ---------- calcrate Envelope Generator & Phase Generator ---------- */ + +/* return : envelope output */ +inline uint OPL_CALC_SLOT(OPL_SLOT *SLOT) { + /* calcrate envelope generator */ + if ((SLOT->evc += SLOT->evs) >= SLOT->eve) { + switch (SLOT->evm) { + case ENV_MOD_AR: /* ATTACK -> DECAY1 */ + /* next DR */ + SLOT->evm = ENV_MOD_DR; + SLOT->evc = EG_DST; + SLOT->eve = SLOT->SL; + SLOT->evs = SLOT->evsd; + break; + case ENV_MOD_DR: /* DECAY -> SL or RR */ + SLOT->evc = SLOT->SL; + SLOT->eve = EG_DED; + if (SLOT->eg_typ) { + SLOT->evs = 0; + } else { + SLOT->evm = ENV_MOD_RR; + SLOT->evs = SLOT->evsr; + } + break; + case ENV_MOD_RR: /* RR -> OFF */ + SLOT->evc = EG_OFF; + SLOT->eve = EG_OFF + 1; + SLOT->evs = 0; + break; + } + } + /* calcrate envelope */ + return SLOT->TLL + ENV_CURVE[SLOT->evc>>ENV_BITS] + (SLOT->ams ? ams : 0); +} + +/* set algorythm connection */ +static void set_algorythm(OPL_CH *CH) { + int *carrier = &outd[0]; + CH->connect1 = CH->CON ? carrier : &feedback2; + CH->connect2 = carrier; +} + +/* ---------- frequency counter for operater update ---------- */ +inline void CALC_FCSLOT(OPL_CH *CH, OPL_SLOT *SLOT) { + int ksr; + + /* frequency step counter */ + SLOT->Incr = CH->fc * SLOT->mul; + ksr = CH->kcode >> SLOT->KSR; + + if (SLOT->ksr != ksr) { + SLOT->ksr = ksr; + /* attack , decay rate recalcration */ + SLOT->evsa = SLOT->AR[ksr]; + SLOT->evsd = SLOT->DR[ksr]; + SLOT->evsr = SLOT->RR[ksr]; + } + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); +} + +/* set multi,am,vib,EG-TYP,KSR,mul */ +inline void set_mul(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot>>1]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + + SLOT->mul = MUL_TABLE[v & 0x0f]; + SLOT->KSR = (v & 0x10) ? 0 : 2; + SLOT->eg_typ = (v & 0x20) >> 5; + SLOT->vib = (v & 0x40); + SLOT->ams = (v & 0x80); + CALC_FCSLOT(CH, SLOT); +} + +/* set ksl & tl */ +inline void set_ksl_tl(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot>>1]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int ksl = v >> 6; /* 0 / 1.5 / 3 / 6 db/OCT */ + + SLOT->ksl = ksl ? 3-ksl : 31; + SLOT->TL = (int)((v & 0x3f) * (0.75 / EG_STEP)); /* 0.75db step */ + + if (!(OPL->mode & 0x80)) { /* not CSM latch total level */ + SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl); + } +} + +/* set attack rate & decay rate */ +inline void set_ar_dr(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot>>1]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int ar = v >> 4; + int dr = v & 0x0f; + + SLOT->AR = ar ? &OPL->AR_TABLE[ar << 2] : RATE_0; + SLOT->evsa = SLOT->AR[SLOT->ksr]; + if (SLOT->evm == ENV_MOD_AR) + SLOT->evs = SLOT->evsa; + + SLOT->DR = dr ? &OPL->DR_TABLE[dr<<2] : RATE_0; + SLOT->evsd = SLOT->DR[SLOT->ksr]; + if (SLOT->evm == ENV_MOD_DR) + SLOT->evs = SLOT->evsd; +} + +/* set sustain level & release rate */ +inline void set_sl_rr(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot>>1]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int sl = v >> 4; + int rr = v & 0x0f; + + SLOT->SL = SL_TABLE[sl]; + if (SLOT->evm == ENV_MOD_DR) + SLOT->eve = SLOT->SL; + SLOT->RR = &OPL->DR_TABLE[rr<<2]; + SLOT->evsr = SLOT->RR[SLOT->ksr]; + if (SLOT->evm == ENV_MOD_RR) + SLOT->evs = SLOT->evsr; +} + +/* operator output calcrator */ + +#define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt + con)>>(24-SIN_ENT_SHIFT)) & (SIN_ENT-1)][env] +/* ---------- calcrate one of channel ---------- */ +inline void OPL_CALC_CH(OPL_CH *CH) { + uint env_out; + OPL_SLOT *SLOT; + + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH->SLOT[SLOT1]; + env_out=OPL_CALC_SLOT(SLOT); + if (env_out < (uint)(EG_ENT - 1)) { + /* PG */ + if (SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT->Cnt += SLOT->Incr; + /* connection */ + if (CH->FB) { + int feedback1 = (CH->op1_out[0] + CH->op1_out[1]) >> CH->FB; + CH->op1_out[1] = CH->op1_out[0]; + *CH->connect1 += CH->op1_out[0] = OP_OUT(SLOT, env_out, feedback1); + } else { + *CH->connect1 += OP_OUT(SLOT, env_out, 0); + } + } else { + CH->op1_out[1] = CH->op1_out[0]; + CH->op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH->SLOT[SLOT2]; + env_out=OPL_CALC_SLOT(SLOT); + if (env_out < (uint)(EG_ENT - 1)) { + /* PG */ + if (SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT->Cnt += SLOT->Incr; + /* connection */ + outd[0] += OP_OUT(SLOT, env_out, feedback2); + } +} + +/* ---------- calcrate rythm block ---------- */ +#define WHITE_NOISE_db 6.0 +inline void OPL_CALC_RH(FM_OPL *OPL, OPL_CH *CH) { + uint env_tam, env_sd, env_top, env_hh; + // This code used to do int(OPL->rnd.getRandomBit() * (WHITE_NOISE_db / EG_STEP)), + // but EG_STEP = 96.0/EG_ENT, and WHITE_NOISE_db=6.0. So, that's equivalent to + // int(OPL->rnd.getRandomBit() * EG_ENT/16). We know that EG_ENT is 4096, or 1024, + // or 128, so we can safely avoid any FP ops. + int whitenoise = OPL->rnd.getRandomBit() * (EG_ENT>>4); + + int tone8; + + OPL_SLOT *SLOT; + int env_out; + + /* BD : same as FM serial mode and output level is large */ + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH[6].SLOT[SLOT1]; + env_out = OPL_CALC_SLOT(SLOT); + if (env_out < EG_ENT-1) { + /* PG */ + if (SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT->Cnt += SLOT->Incr; + /* connection */ + if (CH[6].FB) { + int feedback1 = (CH[6].op1_out[0] + CH[6].op1_out[1]) >> CH[6].FB; + CH[6].op1_out[1] = CH[6].op1_out[0]; + feedback2 = CH[6].op1_out[0] = OP_OUT(SLOT, env_out, feedback1); + } + else { + feedback2 = OP_OUT(SLOT, env_out, 0); + } + } else { + feedback2 = 0; + CH[6].op1_out[1] = CH[6].op1_out[0]; + CH[6].op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH[6].SLOT[SLOT2]; + env_out = OPL_CALC_SLOT(SLOT); + if (env_out < EG_ENT-1) { + /* PG */ + if (SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT->Cnt += SLOT->Incr; + /* connection */ + outd[0] += OP_OUT(SLOT, env_out, feedback2) * 2; + } + + // SD (17) = mul14[fnum7] + white noise + // TAM (15) = mul15[fnum8] + // TOP (18) = fnum6(mul18[fnum8]+whitenoise) + // HH (14) = fnum7(mul18[fnum8]+whitenoise) + white noise + env_sd = OPL_CALC_SLOT(SLOT7_2) + whitenoise; + env_tam =OPL_CALC_SLOT(SLOT8_1); + env_top = OPL_CALC_SLOT(SLOT8_2); + env_hh = OPL_CALC_SLOT(SLOT7_1) + whitenoise; + + /* PG */ + if (SLOT7_1->vib) + SLOT7_1->Cnt += (SLOT7_1->Incr * vib) >> (VIB_RATE_SHIFT-1); + else + SLOT7_1->Cnt += 2 * SLOT7_1->Incr; + if (SLOT7_2->vib) + SLOT7_2->Cnt += (CH[7].fc * vib) >> (VIB_RATE_SHIFT-3); + else + SLOT7_2->Cnt += (CH[7].fc * 8); + if (SLOT8_1->vib) + SLOT8_1->Cnt += (SLOT8_1->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT8_1->Cnt += SLOT8_1->Incr; + if (SLOT8_2->vib) + SLOT8_2->Cnt += ((CH[8].fc * 3) * vib) >> (VIB_RATE_SHIFT-4); + else + SLOT8_2->Cnt += (CH[8].fc * 48); + + tone8 = OP_OUT(SLOT8_2,whitenoise,0 ); + + /* SD */ + if (env_sd < (uint)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT7_1, env_sd, 0) * 8; + /* TAM */ + if (env_tam < (uint)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT8_1, env_tam, 0) * 2; + /* TOP-CY */ + if (env_top < (uint)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT7_2, env_top, tone8) * 2; + /* HH */ + if (env_hh < (uint)(EG_ENT-1)) + outd[0] += OP_OUT(SLOT7_2, env_hh, tone8) * 2; +} + +/* ----------- initialize time tabls ----------- */ +static void init_timetables(FM_OPL *OPL, int ARRATE, int DRRATE) { + int i; + double rate; + + /* make attack rate & decay rate tables */ + for (i = 0; i < 4; i++) + OPL->AR_TABLE[i] = OPL->DR_TABLE[i] = 0; + for (i = 4; i <= 60; i++) { + rate = OPL->freqbase; /* frequency rate */ + if (i < 60) + rate *= 1.0 + (i & 3) * 0.25; /* b0-1 : x1 , x1.25 , x1.5 , x1.75 */ + rate *= 1 << ((i >> 2) - 1); /* b2-5 : shift bit */ + rate *= (double)(EG_ENT << ENV_BITS); + OPL->AR_TABLE[i] = (int)(rate / ARRATE); + OPL->DR_TABLE[i] = (int)(rate / DRRATE); + } + for (i = 60; i < 76; i++) { + OPL->AR_TABLE[i] = EG_AED-1; + OPL->DR_TABLE[i] = OPL->DR_TABLE[60]; + } +} + +/* ---------- generic table initialize ---------- */ +static int OPLOpenTable(void) { + int s,t; + double rate; + int i,j; + double pom; + +#ifdef __DS__ + DS::fastRamReset(); + + TL_TABLE = (int *) DS::fastRamAlloc(TL_MAX * 2 * sizeof(int *)); + SIN_TABLE = (int **) DS::fastRamAlloc(SIN_ENT * 4 * sizeof(int *)); +#else + + /* allocate dynamic tables */ + if ((TL_TABLE = (int *)malloc(TL_MAX * 2 * sizeof(int))) == NULL) + return 0; + + if ((SIN_TABLE = (int **)malloc(SIN_ENT * 4 * sizeof(int *))) == NULL) { + free(TL_TABLE); + return 0; + } +#endif + + if ((AMS_TABLE = (int *)malloc(AMS_ENT * 2 * sizeof(int))) == NULL) { + free(TL_TABLE); + free(SIN_TABLE); + return 0; + } + + if ((VIB_TABLE = (int *)malloc(VIB_ENT * 2 * sizeof(int))) == NULL) { + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + return 0; + } + /* make total level table */ + for (t = 0; t < EG_ENT - 1; t++) { + rate = ((1 << TL_BITS) - 1) / pow(10.0, EG_STEP * t / 20); /* dB -> voltage */ + TL_TABLE[ t] = (int)rate; + TL_TABLE[TL_MAX + t] = -TL_TABLE[t]; + } + /* fill volume off area */ + for (t = EG_ENT - 1; t < TL_MAX; t++) { + TL_TABLE[t] = TL_TABLE[TL_MAX + t] = 0; + } + + /* make sinwave table (total level offet) */ + /* degree 0 = degree 180 = off */ + SIN_TABLE[0] = SIN_TABLE[SIN_ENT /2 ] = &TL_TABLE[EG_ENT - 1]; + for (s = 1;s <= SIN_ENT / 4; s++) { + pom = sin(2 * PI * s / SIN_ENT); /* sin */ + pom = 20 * log10(1 / pom); /* decibel */ + j = int(pom / EG_STEP); /* TL_TABLE steps */ + + /* degree 0 - 90 , degree 180 - 90 : plus section */ + SIN_TABLE[ s] = SIN_TABLE[SIN_ENT / 2 - s] = &TL_TABLE[j]; + /* degree 180 - 270 , degree 360 - 270 : minus section */ + SIN_TABLE[SIN_ENT / 2 + s] = SIN_TABLE[SIN_ENT - s] = &TL_TABLE[TL_MAX + j]; + } + for (s = 0;s < SIN_ENT; s++) { + SIN_TABLE[SIN_ENT * 1 + s] = s < (SIN_ENT / 2) ? SIN_TABLE[s] : &TL_TABLE[EG_ENT]; + SIN_TABLE[SIN_ENT * 2 + s] = SIN_TABLE[s % (SIN_ENT / 2)]; + SIN_TABLE[SIN_ENT * 3 + s] = (s / (SIN_ENT / 4)) & 1 ? &TL_TABLE[EG_ENT] : SIN_TABLE[SIN_ENT * 2 + s]; + } + + + ENV_CURVE = (int *)malloc(sizeof(int) * (2*EG_ENT+1)); + + /* envelope counter -> envelope output table */ + for (i=0; i < EG_ENT; i++) { + /* ATTACK curve */ + pom = pow(((double)(EG_ENT - 1 - i) / EG_ENT), 8) * EG_ENT; + /* if (pom >= EG_ENT) pom = EG_ENT-1; */ + ENV_CURVE[i] = (int)pom; + /* DECAY ,RELEASE curve */ + ENV_CURVE[(EG_DST >> ENV_BITS) + i]= i; + } + /* off */ + ENV_CURVE[EG_OFF >> ENV_BITS]= EG_ENT - 1; + /* make LFO ams table */ + for (i=0; i < AMS_ENT; i++) { + pom = (1.0 + sin(2 * PI * i / AMS_ENT)) / 2; /* sin */ + AMS_TABLE[i] = (int)((1.0 / EG_STEP) * pom); /* 1dB */ + AMS_TABLE[AMS_ENT + i] = (int)((4.8 / EG_STEP) * pom); /* 4.8dB */ + } + /* make LFO vibrate table */ + for (i=0; i < VIB_ENT; i++) { + /* 100cent = 1seminote = 6% ?? */ + pom = (double)VIB_RATE * 0.06 * sin(2 * PI * i / VIB_ENT); /* +-100sect step */ + VIB_TABLE[i] = (int)(VIB_RATE + (pom * 0.07)); /* +- 7cent */ + VIB_TABLE[VIB_ENT + i] = (int)(VIB_RATE + (pom * 0.14)); /* +-14cent */ + } + return 1; +} + +static void OPLCloseTable(void) { + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + free(VIB_TABLE); + free(ENV_CURVE); +} + +/* CSM Key Controll */ +inline void CSMKeyControll(OPL_CH *CH) { + OPL_SLOT *slot1 = &CH->SLOT[SLOT1]; + OPL_SLOT *slot2 = &CH->SLOT[SLOT2]; + /* all key off */ + OPL_KEYOFF(slot1); + OPL_KEYOFF(slot2); + /* total level latch */ + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + /* key on */ + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(slot1); + OPL_KEYON(slot2); +} + +/* ---------- opl initialize ---------- */ +static void OPL_initalize(FM_OPL *OPL) { + int fn; + + /* frequency base */ + OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / OPL->rate) / 72 : 0; + /* Timer base time */ + OPL->TimerBase = 1.0/((double)OPL->clock / 72.0 ); + /* make time tables */ + init_timetables(OPL, OPL_ARRATE, OPL_DRRATE); + /* make fnumber -> increment counter table */ + for (fn=0; fn < 1024; fn++) { + OPL->FN_TABLE[fn] = (uint)(OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2); + } + /* LFO freq.table */ + OPL->amsIncr = (int)(OPL->rate ? (double)AMS_ENT * (1 << AMS_SHIFT) / OPL->rate * 3.7 * ((double)OPL->clock/3600000) : 0); + OPL->vibIncr = (int)(OPL->rate ? (double)VIB_ENT * (1 << VIB_SHIFT) / OPL->rate * 6.4 * ((double)OPL->clock/3600000) : 0); +} + +/* ---------- write a OPL registers ---------- */ +void OPLWriteReg(FM_OPL *OPL, int r, int v) { + OPL_CH *CH; + int slot; + uint block_fnum; + + switch (r & 0xe0) { + case 0x00: /* 00-1f:controll */ + switch (r & 0x1f) { + case 0x01: + /* wave selector enable */ + if (OPL->type&OPL_TYPE_WAVESEL) { + OPL->wavesel = v & 0x20; + if (!OPL->wavesel) { + /* preset compatible mode */ + int c; + for (c = 0; c < OPL->max_ch; c++) { + OPL->P_CH[c].SLOT[SLOT1].wavetable = &SIN_TABLE[0]; + OPL->P_CH[c].SLOT[SLOT2].wavetable = &SIN_TABLE[0]; + } + } + } + return; + case 0x02: /* Timer 1 */ + OPL->T[0] = (256-v) * 4; + break; + case 0x03: /* Timer 2 */ + OPL->T[1] = (256-v) * 16; + return; + case 0x04: /* IRQ clear / mask and Timer enable */ + if (v & 0x80) { /* IRQ flag clear */ + OPL_STATUS_RESET(OPL, 0x7f); + } else { /* set IRQ mask ,timer enable*/ + uint8 st1 = v & 1; + uint8 st2 = (v >> 1) & 1; + /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ + OPL_STATUS_RESET(OPL, v & 0x78); + OPL_STATUSMASK_SET(OPL,((~v) & 0x78) | 0x01); + /* timer 2 */ + if (OPL->st[1] != st2) { + double interval = st2 ? (double)OPL->T[1] * OPL->TimerBase : 0.0; + OPL->st[1] = st2; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 1, interval); + } + /* timer 1 */ + if (OPL->st[0] != st1) { + double interval = st1 ? (double)OPL->T[0] * OPL->TimerBase : 0.0; + OPL->st[0] = st1; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 0, interval); + } + } + return; + } + break; + case 0x20: /* am,vib,ksr,eg type,mul */ + slot = slot_array[r&0x1f]; + if (slot == -1) + return; + set_mul(OPL,slot,v); + return; + case 0x40: + slot = slot_array[r&0x1f]; + if (slot == -1) + return; + set_ksl_tl(OPL,slot,v); + return; + case 0x60: + slot = slot_array[r&0x1f]; + if (slot == -1) + return; + set_ar_dr(OPL,slot,v); + return; + case 0x80: + slot = slot_array[r&0x1f]; + if (slot == -1) + return; + set_sl_rr(OPL,slot,v); + return; + case 0xa0: + switch (r) { + case 0xbd: + /* amsep,vibdep,r,bd,sd,tom,tc,hh */ + { + uint8 rkey = OPL->rythm ^ v; + OPL->ams_table = &AMS_TABLE[v & 0x80 ? AMS_ENT : 0]; + OPL->vib_table = &VIB_TABLE[v & 0x40 ? VIB_ENT : 0]; + OPL->rythm = v & 0x3f; + if (OPL->rythm & 0x20) { + /* BD key on/off */ + if (rkey & 0x10) { + if (v & 0x10) { + OPL->P_CH[6].op1_out[0] = OPL->P_CH[6].op1_out[1] = 0; + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT2]); + } else { + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2]); + } + } + /* SD key on/off */ + if (rkey & 0x08) { + if (v & 0x08) + OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT2]); + else + OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2]); + }/* TAM key on/off */ + if (rkey & 0x04) { + if (v & 0x04) + OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT1]); + else + OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1]); + } + /* TOP-CY key on/off */ + if (rkey & 0x02) { + if (v & 0x02) + OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT2]); + else + OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2]); + } + /* HH key on/off */ + if (rkey & 0x01) { + if (v & 0x01) + OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT1]); + else + OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1]); + } + } + } + return; + + default: + break; + } + /* keyon,block,fnum */ + if ((r & 0x0f) > 8) + return; + CH = &OPL->P_CH[r & 0x0f]; + if (!(r&0x10)) { /* a0-a8 */ + block_fnum = (CH->block_fnum & 0x1f00) | v; + } else { /* b0-b8 */ + int keyon = (v >> 5) & 1; + block_fnum = ((v & 0x1f) << 8) | (CH->block_fnum & 0xff); + if (CH->keyon != keyon) { + if ((CH->keyon=keyon)) { + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(&CH->SLOT[SLOT1]); + OPL_KEYON(&CH->SLOT[SLOT2]); + } else { + OPL_KEYOFF(&CH->SLOT[SLOT1]); + OPL_KEYOFF(&CH->SLOT[SLOT2]); + } + } + } + /* update */ + if (CH->block_fnum != block_fnum) { + int blockRv = 7 - (block_fnum >> 10); + int fnum = block_fnum & 0x3ff; + CH->block_fnum = block_fnum; + CH->ksl_base = KSL_TABLE[block_fnum >> 6]; + CH->fc = OPL->FN_TABLE[fnum] >> blockRv; + CH->kcode = CH->block_fnum >> 9; + if ((OPL->mode & 0x40) && CH->block_fnum & 0x100) + CH->kcode |=1; + CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); + CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); + } + return; + case 0xc0: + /* FB,C */ + if ((r & 0x0f) > 8) + return; + CH = &OPL->P_CH[r&0x0f]; + { + int feedback = (v >> 1) & 7; + CH->FB = feedback ? (8 + 1) - feedback : 0; + CH->CON = v & 1; + set_algorythm(CH); + } + return; + case 0xe0: /* wave type */ + slot = slot_array[r & 0x1f]; + if (slot == -1) + return; + CH = &OPL->P_CH[slot>>1]; + if (OPL->wavesel) { + CH->SLOT[slot&1].wavetable = &SIN_TABLE[(v & 0x03) * SIN_ENT]; + } + return; + } +} + +/* lock/unlock for common table */ +static int OPL_LockTable(void) { + num_lock++; + if (num_lock>1) + return 0; + /* first time */ + cur_chip = NULL; + /* allocate total level table (128kb space) */ + if (!OPLOpenTable()) { + num_lock--; + return -1; + } + return 0; +} + +static void OPL_UnLockTable(void) { + if (num_lock) + num_lock--; + if (num_lock) + return; + /* last time */ + cur_chip = NULL; + OPLCloseTable(); +} + +/*******************************************************************************/ +/* YM3812 local section */ +/*******************************************************************************/ + +/* ---------- update one of chip ----------- */ +void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length) { + int i; + int data; + int16 *buf = buffer; + uint amsCnt = OPL->amsCnt; + uint vibCnt = OPL->vibCnt; + uint8 rythm = OPL->rythm & 0x20; + OPL_CH *CH, *R_CH; + + + if ((void *)OPL != cur_chip) { + cur_chip = (void *)OPL; + /* channel pointers */ + S_CH = OPL->P_CH; + E_CH = &S_CH[9]; + /* rythm slot */ + SLOT7_1 = &S_CH[7].SLOT[SLOT1]; + SLOT7_2 = &S_CH[7].SLOT[SLOT2]; + SLOT8_1 = &S_CH[8].SLOT[SLOT1]; + SLOT8_2 = &S_CH[8].SLOT[SLOT2]; + /* LFO state */ + amsIncr = OPL->amsIncr; + vibIncr = OPL->vibIncr; + ams_table = OPL->ams_table; + vib_table = OPL->vib_table; + } + R_CH = rythm ? &S_CH[6] : E_CH; + for (i = 0; i < length; i++) { + /* channel A channel B channel C */ + /* LFO */ + ams = ams_table[(amsCnt += amsIncr) >> AMS_SHIFT]; + vib = vib_table[(vibCnt += vibIncr) >> VIB_SHIFT]; + outd[0] = 0; + /* FM part */ + for (CH = S_CH; CH < R_CH; CH++) + OPL_CALC_CH(CH); + /* Rythn part */ + if (rythm) + OPL_CALC_RH(OPL, S_CH); + /* limit check */ + data = CLIP(outd[0], OPL_MINOUT, OPL_MAXOUT); + /* store to sound buffer */ + buf[i] = data >> OPL_OUTSB; + } + + OPL->amsCnt = amsCnt; + OPL->vibCnt = vibCnt; +} + +/* ---------- reset a chip ---------- */ +void OPLResetChip(FM_OPL *OPL) { + int c,s; + int i; + + /* reset chip */ + OPL->mode = 0; /* normal mode */ + OPL_STATUS_RESET(OPL, 0x7f); + /* reset with register write */ + OPLWriteReg(OPL, 0x01,0); /* wabesel disable */ + OPLWriteReg(OPL, 0x02,0); /* Timer1 */ + OPLWriteReg(OPL, 0x03,0); /* Timer2 */ + OPLWriteReg(OPL, 0x04,0); /* IRQ mask clear */ + for (i = 0xff; i >= 0x20; i--) + OPLWriteReg(OPL,i,0); + /* reset OPerator parameter */ + for (c = 0; c < OPL->max_ch; c++) { + OPL_CH *CH = &OPL->P_CH[c]; + /* OPL->P_CH[c].PAN = OPN_CENTER; */ + for (s = 0; s < 2; s++) { + /* wave table */ + CH->SLOT[s].wavetable = &SIN_TABLE[0]; + /* CH->SLOT[s].evm = ENV_MOD_RR; */ + CH->SLOT[s].evc = EG_OFF; + CH->SLOT[s].eve = EG_OFF + 1; + CH->SLOT[s].evs = 0; + } + } +} + +/* ---------- Create a virtual YM3812 ---------- */ +/* 'rate' is sampling rate and 'bufsiz' is the size of the */ +FM_OPL *OPLCreate(int type, int clock, int rate) { + char *ptr; + FM_OPL *OPL; + int state_size; + int max_ch = 9; /* normaly 9 channels */ + + if (OPL_LockTable() == -1) + return NULL; + /* allocate OPL state space */ + state_size = sizeof(FM_OPL); + state_size += sizeof(OPL_CH) * max_ch; + + /* allocate memory block */ + ptr = (char *)calloc(state_size, 1); + if (ptr == NULL) + return NULL; + + /* clear */ + memset(ptr, 0, state_size); + OPL = (FM_OPL *)ptr; ptr += sizeof(FM_OPL); + OPL->P_CH = (OPL_CH *)ptr; ptr += sizeof(OPL_CH) * max_ch; + + /* set channel state pointer */ + OPL->type = type; + OPL->clock = clock; + OPL->rate = rate; + OPL->max_ch = max_ch; + + /* init grobal tables */ + OPL_initalize(OPL); + + /* reset chip */ + OPLResetChip(OPL); + return OPL; +} + +/* ---------- Destroy one of vietual YM3812 ---------- */ +void OPLDestroy(FM_OPL *OPL) { + OPL_UnLockTable(); + free(OPL); +} + +/* ---------- Option handlers ---------- */ +void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler,int channelOffset) { + OPL->TimerHandler = TimerHandler; + OPL->TimerParam = channelOffset; +} + +void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param) { + OPL->IRQHandler = IRQHandler; + OPL->IRQParam = param; +} + +void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler,int param) { + OPL->UpdateHandler = UpdateHandler; + OPL->UpdateParam = param; +} + +/* ---------- YM3812 I/O interface ---------- */ +int OPLWrite(FM_OPL *OPL,int a,int v) { + if (!(a & 1)) { /* address port */ + OPL->address = v & 0xff; + } else { /* data port */ + if (OPL->UpdateHandler) + OPL->UpdateHandler(OPL->UpdateParam,0); + OPLWriteReg(OPL, OPL->address,v); + } + return OPL->status >> 7; +} + +unsigned char OPLRead(FM_OPL *OPL,int a) { + if (!(a & 1)) { /* status port */ + return OPL->status & (OPL->statusmask | 0x80); + } + /* data port */ + switch (OPL->address) { + case 0x05: /* KeyBoard IN */ + warning("OPL:read unmapped KEYBOARD port"); + return 0; + case 0x19: /* I/O DATA */ + warning("OPL:read unmapped I/O port"); + return 0; + case 0x1a: /* PCM-DATA */ + return 0; + default: + break; + } + return 0; +} + +int OPLTimerOver(FM_OPL *OPL, int c) { + if (c) { /* Timer B */ + OPL_STATUS_SET(OPL, 0x20); + } else { /* Timer A */ + OPL_STATUS_SET(OPL, 0x40); + /* CSM mode key,TL controll */ + if (OPL->mode & 0x80) { /* CSM mode total level latch and auto key on */ + int ch; + if (OPL->UpdateHandler) + OPL->UpdateHandler(OPL->UpdateParam,0); + for (ch = 0; ch < 9; ch++) + CSMKeyControll(&OPL->P_CH[ch]); + } + } + /* reload timer */ + if (OPL->TimerHandler) + (OPL->TimerHandler)(OPL->TimerParam + c, (double)OPL->T[c] * OPL->TimerBase); + return OPL->status >> 7; +} + +FM_OPL *makeAdLibOPL(int rate) { + // We need to emulate one YM3812 chip + int env_bits = FMOPL_ENV_BITS_HQ; + int eg_ent = FMOPL_EG_ENT_HQ; +#if defined (_WIN32_WCE) || defined(__SYMBIAN32__) || defined(__GP32__) || defined (GP2X) || defined(__MAEMO__) || defined(__DS__) || defined (__MINT__) || defined(__N64__) + if (ConfMan.hasKey("FM_high_quality") && ConfMan.getBool("FM_high_quality")) { + env_bits = FMOPL_ENV_BITS_HQ; + eg_ent = FMOPL_EG_ENT_HQ; + } else if (ConfMan.hasKey("FM_medium_quality") && ConfMan.getBool("FM_medium_quality")) { + env_bits = FMOPL_ENV_BITS_MQ; + eg_ent = FMOPL_EG_ENT_MQ; + } else { + env_bits = FMOPL_ENV_BITS_LQ; + eg_ent = FMOPL_EG_ENT_LQ; + } +#endif + + OPLBuildTables(env_bits, eg_ent); + return OPLCreate(OPL_TYPE_YM3812, 3579545, rate); +} + +} // End of namespace MAME +} // End of namespace OPL + diff --git a/audio/softsynth/opl/mame.h b/audio/softsynth/opl/mame.h new file mode 100644 index 0000000000..58ef5f18dc --- /dev/null +++ b/audio/softsynth/opl/mame.h @@ -0,0 +1,202 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * LGPL licensed version of MAMEs fmopl (V0.37a modified) by + * Tatsuyuki Satoh. Included from LGPL'ed AdPlug. + */ + + +#ifndef SOUND_SOFTSYNTH_OPL_MAME_H +#define SOUND_SOFTSYNTH_OPL_MAME_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/random.h" + +#include "audio/fmopl.h" + +namespace OPL { +namespace MAME { + +enum { + FMOPL_ENV_BITS_HQ = 16, + FMOPL_ENV_BITS_MQ = 8, + FMOPL_ENV_BITS_LQ = 8, + FMOPL_EG_ENT_HQ = 4096, + FMOPL_EG_ENT_MQ = 1024, + FMOPL_EG_ENT_LQ = 128 +}; + + +typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); +typedef void (*OPL_IRQHANDLER)(int param,int irq); +typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); + +#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ + +/* Saving is necessary for member of the 'R' mark for suspend/resume */ +/* ---------- OPL one of slot ---------- */ +typedef struct fm_opl_slot { + int TL; /* total level :TL << 8 */ + int TLL; /* adjusted now TL */ + uint8 KSR; /* key scale rate :(shift down bit) */ + int *AR; /* attack rate :&AR_TABLE[AR<<2] */ + int *DR; /* decay rate :&DR_TABLE[DR<<2] */ + int SL; /* sustain level :SL_TABLE[SL] */ + int *RR; /* release rate :&DR_TABLE[RR<<2] */ + uint8 ksl; /* keyscale level :(shift down bits) */ + uint8 ksr; /* key scale rate :kcode>>KSR */ + uint mul; /* multiple :ML_TABLE[ML] */ + uint Cnt; /* frequency count */ + uint Incr; /* frequency step */ + + /* envelope generator state */ + uint8 eg_typ;/* envelope type flag */ + uint8 evm; /* envelope phase */ + int evc; /* envelope counter */ + int eve; /* envelope counter end point */ + int evs; /* envelope counter step */ + int evsa; /* envelope step for AR :AR[ksr] */ + int evsd; /* envelope step for DR :DR[ksr] */ + int evsr; /* envelope step for RR :RR[ksr] */ + + /* LFO */ + uint8 ams; /* ams flag */ + uint8 vib; /* vibrate flag */ + /* wave selector */ + int **wavetable; +} OPL_SLOT; + +/* ---------- OPL one of channel ---------- */ +typedef struct fm_opl_channel { + OPL_SLOT SLOT[2]; + uint8 CON; /* connection type */ + uint8 FB; /* feed back :(shift down bit)*/ + int *connect1; /* slot1 output pointer */ + int *connect2; /* slot2 output pointer */ + int op1_out[2]; /* slot1 output for selfeedback */ + + /* phase generator state */ + uint block_fnum; /* block+fnum */ + uint8 kcode; /* key code : KeyScaleCode */ + uint fc; /* Freq. Increment base */ + uint ksl_base; /* KeyScaleLevel Base step */ + uint8 keyon; /* key on/off flag */ +} OPL_CH; + +/* OPL state */ +typedef struct fm_opl_f { + uint8 type; /* chip type */ + int clock; /* master clock (Hz) */ + int rate; /* sampling rate (Hz) */ + double freqbase; /* frequency base */ + double TimerBase; /* Timer base time (==sampling time) */ + uint8 address; /* address register */ + uint8 status; /* status flag */ + uint8 statusmask; /* status mask */ + uint mode; /* Reg.08 : CSM , notesel,etc. */ + + /* Timer */ + int T[2]; /* timer counter */ + uint8 st[2]; /* timer enable */ + + /* FM channel slots */ + OPL_CH *P_CH; /* pointer of CH */ + int max_ch; /* maximum channel */ + + /* Rythm sention */ + uint8 rythm; /* Rythm mode , key flag */ + + /* time tables */ + int AR_TABLE[76]; /* atttack rate tables */ + int DR_TABLE[76]; /* decay rate tables */ + uint FN_TABLE[1024];/* fnumber -> increment counter */ + + /* LFO */ + int *ams_table; + int *vib_table; + int amsCnt; + int amsIncr; + int vibCnt; + int vibIncr; + + /* wave selector enable flag */ + uint8 wavesel; + + /* external event callback handler */ + OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ + int TimerParam; /* TIMER parameter */ + OPL_IRQHANDLER IRQHandler; /* IRQ handler */ + int IRQParam; /* IRQ parameter */ + OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */ + int UpdateParam; /* stream update parameter */ + + Common::RandomSource rnd; +} FM_OPL; + +/* ---------- Generic interface section ---------- */ +#define OPL_TYPE_YM3526 (0) +#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) + +void OPLBuildTables(int ENV_BITS_PARAM, int EG_ENT_PARAM); + +FM_OPL *OPLCreate(int type, int clock, int rate); +void OPLDestroy(FM_OPL *OPL); +void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler, int channelOffset); +void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param); +void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler, int param); + +void OPLResetChip(FM_OPL *OPL); +int OPLWrite(FM_OPL *OPL, int a, int v); +unsigned char OPLRead(FM_OPL *OPL, int a); +int OPLTimerOver(FM_OPL *OPL, int c); +void OPLWriteReg(FM_OPL *OPL, int r, int v); +void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length); + +// Factory method +FM_OPL *makeAdLibOPL(int rate); + +// OPL API implementation +class OPL : public ::OPL::OPL { +private: + FM_OPL *_opl; +public: + OPL() : _opl(0) {} + ~OPL(); + + bool init(int rate); + void reset(); + + void write(int a, int v); + byte read(int a); + + void writeReg(int r, int v); + + void readBuffer(int16 *buffer, int length); + bool isStereo() const { return false; } +}; + +} // End of namespace MAME +} // End of namespace OPL + +#endif diff --git a/audio/softsynth/pcspk.cpp b/audio/softsynth/pcspk.cpp new file mode 100644 index 0000000000..69ba113c8b --- /dev/null +++ b/audio/softsynth/pcspk.cpp @@ -0,0 +1,187 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* $URL$ +* $Id$ +* +*/ + +#include "audio/softsynth/pcspk.h" +#include "audio/null.h" + +namespace Audio { + +const PCSpeaker::generatorFunc PCSpeaker::generateWave[] = + {&PCSpeaker::generateSquare, &PCSpeaker::generateSine, + &PCSpeaker::generateSaw, &PCSpeaker::generateTriangle}; + +PCSpeaker::PCSpeaker(int rate) { + _rate = rate; + _wave = kWaveFormSquare; + _playForever = false; + _oscLength = 0; + _oscSamples = 0; + _remainingSamples = 0; + _mixedSamples = 0; + _volume = 255; +} + +PCSpeaker::~PCSpeaker() { +} + +void PCSpeaker::play(WaveForm wave, int freq, int32 length) { + Common::StackLock lock(_mutex); + + assert((wave >= kWaveFormSquare) && (wave <= kWaveFormTriangle)); + + _wave = wave; + _oscLength = _rate / freq; + _oscSamples = 0; + if (length == -1) { + _remainingSamples = 1; + _playForever = true; + } else { + _remainingSamples = (_rate * length) / 1000; + _playForever = false; + } + _mixedSamples = 0; +} + +void PCSpeaker::stop(int32 delay) { + Common::StackLock lock(_mutex); + + _remainingSamples = (_rate * delay) / 1000; + _playForever = false; +} + +void PCSpeaker::setVolume(byte volume) { + _volume = volume; +} + +bool PCSpeaker::isPlaying() const { + return _remainingSamples != 0; +} + +int PCSpeaker::readBuffer(int16 *buffer, const int numSamples) { + Common::StackLock lock(_mutex); + + int i; + + for (i = 0; _remainingSamples && (i < numSamples); i++) { + buffer[i] = generateWave[_wave](_oscSamples, _oscLength) * _volume; + if (_oscSamples++ >= _oscLength) + _oscSamples = 0; + if (!_playForever) + _remainingSamples--; + _mixedSamples++; + } + + // Clear the rest of the buffer + if (i < numSamples) + memset(buffer + i, 0, (numSamples - i) * sizeof(int16)); + + return numSamples; +} + +int8 PCSpeaker::generateSquare(uint32 x, uint32 oscLength) { + return (x < (oscLength / 2)) ? 127 : -128; +} + +int8 PCSpeaker::generateSine(uint32 x, uint32 oscLength) { + if (oscLength == 0) + return 0; + + // TODO: Maybe using a look-up-table would be better? + return CLIP((int16) (128 * sin(2.0 * PI * x / oscLength)), -128, 127); +} + +int8 PCSpeaker::generateSaw(uint32 x, uint32 oscLength) { + if (oscLength == 0) + return 0; + + return ((x * (65536 / oscLength)) >> 8) - 128; +} + +int8 PCSpeaker::generateTriangle(uint32 x, uint32 oscLength) { + if (oscLength == 0) + return 0; + + int y = ((x * (65536 / (oscLength / 2))) >> 8) - 128; + + return (x <= (oscLength / 2)) ? y : (256 - y); +} + +} // End of namespace Audio + + +// Plugin interface +// (This can only create a null driver since pc speaker support is not part of the +// midi driver architecture. But we need the plugin for the options menu in the launcher +// and for MidiDriver::detectDevice() which is more or less used by all engines.) + +class PCSpeakerMusicPlugin : public NullMusicPlugin { +public: + const char *getName() const { + return _s("PC Speaker Emulator"); + } + + const char *getId() const { + return "pcspk"; + } + + MusicDevices getDevices() const; +}; + +MusicDevices PCSpeakerMusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_PCSPK)); + return devices; +} + +class PCjrMusicPlugin : public NullMusicPlugin { +public: + const char *getName() const { + return _s("IBM PCjr Emulator"); + } + + const char *getId() const { + return "pcjr"; + } + + MusicDevices getDevices() const; +}; + +MusicDevices PCjrMusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_PCJR)); + return devices; +} + +//#if PLUGIN_ENABLED_DYNAMIC(PCSPK) + //REGISTER_PLUGIN_DYNAMIC(PCSPK, PLUGIN_TYPE_MUSIC, PCSpeakerMusicPlugin); +//#else + REGISTER_PLUGIN_STATIC(PCSPK, PLUGIN_TYPE_MUSIC, PCSpeakerMusicPlugin); +//#endif + +//#if PLUGIN_ENABLED_DYNAMIC(PCJR) + //REGISTER_PLUGIN_DYNAMIC(PCJR, PLUGIN_TYPE_MUSIC, PCjrMusicPlugin); +//#else + REGISTER_PLUGIN_STATIC(PCJR, PLUGIN_TYPE_MUSIC, PCjrMusicPlugin); +//#endif diff --git a/audio/softsynth/pcspk.h b/audio/softsynth/pcspk.h new file mode 100644 index 0000000000..c0d85bbceb --- /dev/null +++ b/audio/softsynth/pcspk.h @@ -0,0 +1,88 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef SOUND_SOFTSYNTH_PCSPK_H +#define SOUND_SOFTSYNTH_PCSPK_H + +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "common/mutex.h" + +namespace Audio { + +class PCSpeaker : public AudioStream { +public: + enum WaveForm { + kWaveFormSquare = 0, + kWaveFormSine, + kWaveFormSaw, + kWaveFormTriangle + }; + + PCSpeaker(int rate = 44100); + ~PCSpeaker(); + + /** Play a note for length ms. + * + * If length is negative, play until told to stop. + */ + void play(WaveForm wave, int freq, int32 length); + /** Stop the currently playing note after delay ms. */ + void stop(int32 delay = 0); + /** Adjust the volume. */ + void setVolume(byte volume); + + bool isPlaying() const; + + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { return false; } + bool endOfData() const { return false; } + bool endOfStream() const { return false; } + int getRate() const { return _rate; } + +protected: + Common::Mutex _mutex; + + int _rate; + WaveForm _wave; + bool _playForever; + uint32 _oscLength; + uint32 _oscSamples; + uint32 _remainingSamples; + uint32 _mixedSamples; + byte _volume; + + typedef int8 (*generatorFunc)(uint32, uint32); + static const generatorFunc generateWave[]; + + static int8 generateSquare(uint32 x, uint32 oscLength); + static int8 generateSine(uint32 x, uint32 oscLength); + static int8 generateSaw(uint32 x, uint32 oscLength); + static int8 generateTriangle(uint32 x, uint32 oscLength); +}; + +} // End of namespace Audio + +#endif // SOUND_SOFTSYNTH_PCSPEAKER_H diff --git a/audio/softsynth/sid.cpp b/audio/softsynth/sid.cpp new file mode 100644 index 0000000000..241766fa0a --- /dev/null +++ b/audio/softsynth/sid.cpp @@ -0,0 +1,1456 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This file is based on reSID, a MOS6581 SID emulator engine. + * Copyright (C) 2004 Dag Lem + */ + +#ifndef DISABLE_SID + +#include "sid.h" +#include "audio/null.h" +#include + +namespace Resid { + +// Fixpoint constants (16.16 bits). +const int SID::FIXP_SHIFT = 16; +const int SID::FIXP_MASK = 0xffff; + +/* + * WaveformGenerator + */ + +WaveformGenerator::WaveformGenerator() { + sync_source = this; + + reset(); +} + +void WaveformGenerator::set_sync_source(WaveformGenerator* source) { + sync_source = source; + source->sync_dest = this; +} + +void WaveformGenerator::writeFREQ_LO(reg8 freq_lo) { + freq = (freq & 0xff00) | (freq_lo & 0x00ff); +} + +void WaveformGenerator::writeFREQ_HI(reg8 freq_hi) { + freq = ((freq_hi << 8) & 0xff00) | (freq & 0x00ff); +} + +void WaveformGenerator::writePW_LO(reg8 pw_lo) { + pw = (pw & 0xf00) | (pw_lo & 0x0ff); +} + +void WaveformGenerator::writePW_HI(reg8 pw_hi) { + pw = ((pw_hi << 8) & 0xf00) | (pw & 0x0ff); +} + +void WaveformGenerator::writeCONTROL_REG(reg8 control) { + waveform = (control >> 4) & 0x0f; + ring_mod = control & 0x04; + sync = control & 0x02; + + reg8 test_next = control & 0x08; + + // Test bit set. + if (test_next) { + accumulator = 0; + shift_register = 0; + } + // Test bit cleared. + else if (test) { + shift_register = 0x7ffff8; + } + + test = test_next; + + // The gate bit is handled by the EnvelopeGenerator. +} + +reg8 WaveformGenerator::readOSC() { + return output() >> 4; +} + +void WaveformGenerator::reset() { + accumulator = 0; + shift_register = 0x7ffff8; + freq = 0; + pw = 0; + + test = 0; + ring_mod = 0; + sync = 0; + + msb_rising = false; +} + +RESID_INLINE void WaveformGenerator::clock(cycle_count delta_t) { + // No operation if test bit is set. + if (test) { + return; + } + + reg24 accumulator_prev = accumulator; + + // Calculate new accumulator value; + reg24 delta_accumulator = delta_t*freq; + accumulator += delta_accumulator; + accumulator &= 0xffffff; + + // Check whether the MSB is set high. This is used for synchronization. + msb_rising = !(accumulator_prev & 0x800000) && (accumulator & 0x800000); + + // Shift noise register once for each time accumulator bit 19 is set high. + // Bit 19 is set high each time 2^20 (0x100000) is added to the accumulator. + reg24 shift_period = 0x100000; + + while (delta_accumulator) { + if (delta_accumulator < shift_period) { + shift_period = delta_accumulator; + // Determine whether bit 19 is set on the last period. + // NB! Requires two's complement integer. + if (shift_period <= 0x080000) { + // Check for flip from 0 to 1. + if (((accumulator - shift_period) & 0x080000) || !(accumulator & 0x080000)) + { + break; + } + } + else { + // Check for flip from 0 (to 1 or via 1 to 0) or from 1 via 0 to 1. + if (((accumulator - shift_period) & 0x080000) && !(accumulator & 0x080000)) + { + break; + } + } + } + + // Shift the noise/random register. + // NB! The shift is actually delayed 2 cycles, this is not modeled. + reg24 bit0 = ((shift_register >> 22) ^ (shift_register >> 17)) & 0x1; + shift_register <<= 1; + shift_register &= 0x7fffff; + shift_register |= bit0; + + delta_accumulator -= shift_period; + } +} + + +/** + * Synchronize oscillators. + * This must be done after all the oscillators have been clock()'ed since the + * oscillators operate in parallel. + * Note that the oscillators must be clocked exactly on the cycle when the + * MSB is set high for hard sync to operate correctly. See SID::clock(). + */ +RESID_INLINE void WaveformGenerator::synchronize() { + // A special case occurs when a sync source is synced itself on the same + // cycle as when its MSB is set high. In this case the destination will + // not be synced. This has been verified by sampling OSC3. + if (msb_rising && sync_dest->sync && !(sync && sync_source->msb_rising)) { + sync_dest->accumulator = 0; + } +} + + +/* + * Output functions + */ + +// No waveform: Zero output. +RESID_INLINE reg12 WaveformGenerator::output____() { + return 0x000; +} + +// Triangle: +RESID_INLINE reg12 WaveformGenerator::output___T() { + reg24 msb = (ring_mod ? accumulator ^ sync_source->accumulator : accumulator) + & 0x800000; + return ((msb ? ~accumulator : accumulator) >> 11) & 0xfff; +} + +// Sawtooth: +RESID_INLINE reg12 WaveformGenerator::output__S_() { + return accumulator >> 12; +} + +// Pulse: +RESID_INLINE reg12 WaveformGenerator::output_P__() { + return (test || (accumulator >> 12) >= pw) ? 0xfff : 0x000; +} + +// Noise: +RESID_INLINE reg12 WaveformGenerator::outputN___() { + return + ((shift_register & 0x400000) >> 11) | + ((shift_register & 0x100000) >> 10) | + ((shift_register & 0x010000) >> 7) | + ((shift_register & 0x002000) >> 5) | + ((shift_register & 0x000800) >> 4) | + ((shift_register & 0x000080) >> 1) | + ((shift_register & 0x000010) << 1) | + ((shift_register & 0x000004) << 2); +} + +// Combined waveforms: + +RESID_INLINE reg12 WaveformGenerator::output__ST() { + return wave6581__ST[output__S_()] << 4; +} + +RESID_INLINE reg12 WaveformGenerator::output_P_T() { + return (wave6581_P_T[output___T() >> 1] << 4) & output_P__(); +} + +RESID_INLINE reg12 WaveformGenerator::output_PS_() { + return (wave6581_PS_[output__S_()] << 4) & output_P__(); +} + +RESID_INLINE reg12 WaveformGenerator::output_PST() { + return (wave6581_PST[output__S_()] << 4) & output_P__(); +} + +// Combined waveforms including noise: + +RESID_INLINE reg12 WaveformGenerator::outputN__T() { + return 0; +} + +RESID_INLINE reg12 WaveformGenerator::outputN_S_() { + return 0; +} + +RESID_INLINE reg12 WaveformGenerator::outputN_ST() { + return 0; +} + +RESID_INLINE reg12 WaveformGenerator::outputNP__() { + return 0; +} + +RESID_INLINE reg12 WaveformGenerator::outputNP_T() { + return 0; +} + +RESID_INLINE reg12 WaveformGenerator::outputNPS_() { + return 0; +} + +RESID_INLINE reg12 WaveformGenerator::outputNPST() { + return 0; +} + +/** + * Select one of 16 possible combinations of waveforms. + */ +RESID_INLINE reg12 WaveformGenerator::output() { + // It may seem cleaner to use an array of member functions to return + // waveform output; however a switch with inline functions is faster. + + switch (waveform) { + default: + case 0x0: + return output____(); + case 0x1: + return output___T(); + case 0x2: + return output__S_(); + case 0x3: + return output__ST(); + case 0x4: + return output_P__(); + case 0x5: + return output_P_T(); + case 0x6: + return output_PS_(); + case 0x7: + return output_PST(); + case 0x8: + return outputN___(); + case 0x9: + return outputN__T(); + case 0xa: + return outputN_S_(); + case 0xb: + return outputN_ST(); + case 0xc: + return outputNP__(); + case 0xd: + return outputNP_T(); + case 0xe: + return outputNPS_(); + case 0xf: + return outputNPST(); + } +} + +/* + * Our objective is to construct a smooth interpolating single-valued function + * y = f(x). + * Our approach is to approximate the properties of Catmull-Rom splines for + * piecewice cubic polynomials. + */ + +/** + * Calculation of coefficients. + */ +inline void cubic_coefficients(double x1, double y1, double x2, double y2, + double k1, double k2, + double& a, double& b, double& c, double& d) +{ + double dx = x2 - x1, dy = y2 - y1; + + a = ((k1 + k2) - 2*dy/dx)/(dx*dx); + b = ((k2 - k1)/dx - 3*(x1 + x2)*a)/2; + c = k1 - (3*x1*a + 2*b)*x1; + d = y1 - ((x1*a + b)*x1 + c)*x1; +} + +/** + * Evaluation of cubic polynomial by forward differencing. + */ +template +inline void interpolate_segment(double x1, double y1, double x2, double y2, + double k1, double k2, + PointPlotter plot, double res) +{ + double a, b, c, d; + cubic_coefficients(x1, y1, x2, y2, k1, k2, a, b, c, d); + + double y = ((a*x1 + b)*x1 + c)*x1 + d; + double dy = (3*a*(x1 + res) + 2*b)*x1*res + ((a*res + b)*res + c)*res; + double d2y = (6*a*(x1 + res) + 2*b)*res*res; + double d3y = 6*a*res*res*res; + + // Calculate each point. + for (double x = x1; x <= x2; x += res) { + plot(x, y); + y += dy; dy += d2y; d2y += d3y; + } +} + +template +inline double x(PointIter p) { + return (*p)[0]; +} + +template +inline double y(PointIter p) { + return (*p)[1]; +} + +/** + * Evaluation of complete interpolating function. + * Note that since each curve segment is controlled by four points, the + * end points will not be interpolated. If extra control points are not + * desirable, the end points can simply be repeated to ensure interpolation. + * Note also that points of non-differentiability and discontinuity can be + * introduced by repeating points. + */ +template +inline void interpolate(PointIter p0, PointIter pn, PointPlotter plot, double res) { + double k1, k2; + + // Set up points for first curve segment. + PointIter p1 = p0; ++p1; + PointIter p2 = p1; ++p2; + PointIter p3 = p2; ++p3; + + // Draw each curve segment. + for (; p2 != pn; ++p0, ++p1, ++p2, ++p3) { + // p1 and p2 equal; single point. + if (x(p1) == x(p2)) { + continue; + } + // Both end points repeated; straight line. + if (x(p0) == x(p1) && x(p2) == x(p3)) { + k1 = k2 = (y(p2) - y(p1))/(x(p2) - x(p1)); + } + // p0 and p1 equal; use f''(x1) = 0. + else if (x(p0) == x(p1)) { + k2 = (y(p3) - y(p1))/(x(p3) - x(p1)); + k1 = (3*(y(p2) - y(p1))/(x(p2) - x(p1)) - k2)/2; + } + // p2 and p3 equal; use f''(x2) = 0. + else if (x(p2) == x(p3)) { + k1 = (y(p2) - y(p0))/(x(p2) - x(p0)); + k2 = (3*(y(p2) - y(p1))/(x(p2) - x(p1)) - k1)/2; + } + // Normal curve. + else { + k1 = (y(p2) - y(p0))/(x(p2) - x(p0)); + k2 = (y(p3) - y(p1))/(x(p3) - x(p1)); + } + + interpolate_segment(x(p1), y(p1), x(p2), y(p2), k1, k2, plot, res); + } +} + +/** + * Class for plotting integers into an array. + */ +template +class PointPlotter { +protected: + F* f; + +public: + PointPlotter(F* arr) : f(arr) { + } + + void operator ()(double x, double y) { + // Clamp negative values to zero. + if (y < 0) { + y = 0; + } + + f[F(x)] = F(y); + } +}; + +fc_point Filter::f0_points_6581[] = { + // FC f FCHI FCLO + // ---------------------------- + { 0, 220 }, // 0x00 - repeated end point + { 0, 220 }, // 0x00 + { 128, 230 }, // 0x10 + { 256, 250 }, // 0x20 + { 384, 300 }, // 0x30 + { 512, 420 }, // 0x40 + { 640, 780 }, // 0x50 + { 768, 1600 }, // 0x60 + { 832, 2300 }, // 0x68 + { 896, 3200 }, // 0x70 + { 960, 4300 }, // 0x78 + { 992, 5000 }, // 0x7c + { 1008, 5400 }, // 0x7e + { 1016, 5700 }, // 0x7f + { 1023, 6000 }, // 0x7f 0x07 + { 1023, 6000 }, // 0x7f 0x07 - discontinuity + { 1024, 4600 }, // 0x80 - + { 1024, 4600 }, // 0x80 + { 1032, 4800 }, // 0x81 + { 1056, 5300 }, // 0x84 + { 1088, 6000 }, // 0x88 + { 1120, 6600 }, // 0x8c + { 1152, 7200 }, // 0x90 + { 1280, 9500 }, // 0xa0 + { 1408, 12000 }, // 0xb0 + { 1536, 14500 }, // 0xc0 + { 1664, 16000 }, // 0xd0 + { 1792, 17100 }, // 0xe0 + { 1920, 17700 }, // 0xf0 + { 2047, 18000 }, // 0xff 0x07 + { 2047, 18000 } // 0xff 0x07 - repeated end point +}; + + +/* + * Filter + */ + +Filter::Filter() { + fc = 0; + + res = 0; + + filt = 0; + + voice3off = 0; + + hp_bp_lp = 0; + + vol = 0; + + // State of filter. + Vhp = 0; + Vbp = 0; + Vlp = 0; + Vnf = 0; + + enable_filter(true); + + // Create mappings from FC to cutoff frequency. + interpolate(f0_points_6581, f0_points_6581 + + sizeof(f0_points_6581)/sizeof(*f0_points_6581) - 1, + PointPlotter(f0_6581), 1.0); + + mixer_DC = (-0xfff*0xff/18) >> 7; + + f0 = f0_6581; + f0_points = f0_points_6581; + f0_count = sizeof(f0_points_6581)/sizeof(*f0_points_6581); + + set_w0(); + set_Q(); +} + +void Filter::enable_filter(bool enable) { + enabled = enable; +} + +void Filter::reset(){ + fc = 0; + + res = 0; + + filt = 0; + + voice3off = 0; + + hp_bp_lp = 0; + + vol = 0; + + // State of filter. + Vhp = 0; + Vbp = 0; + Vlp = 0; + Vnf = 0; + + set_w0(); + set_Q(); +} + +void Filter::writeFC_LO(reg8 fc_lo) { + fc = (fc & 0x7f8) | (fc_lo & 0x007); + set_w0(); +} + +void Filter::writeFC_HI(reg8 fc_hi) { + fc = ((fc_hi << 3) & 0x7f8) | (fc & 0x007); + set_w0(); +} + +void Filter::writeRES_FILT(reg8 res_filt) { + res = (res_filt >> 4) & 0x0f; + set_Q(); + + filt = res_filt & 0x0f; +} + +void Filter::writeMODE_VOL(reg8 mode_vol) { + voice3off = mode_vol & 0x80; + + hp_bp_lp = (mode_vol >> 4) & 0x07; + + vol = mode_vol & 0x0f; +} + +// Set filter cutoff frequency. +void Filter::set_w0() { + const double pi = 3.1415926535897932385; + + // Multiply with 1.048576 to facilitate division by 1 000 000 by right- + // shifting 20 times (2 ^ 20 = 1048576). + w0 = static_cast(2*pi*f0[fc]*1.048576); + + // Limit f0 to 16kHz to keep 1 cycle filter stable. + const sound_sample w0_max_1 = static_cast(2*pi*16000*1.048576); + w0_ceil_1 = w0 <= w0_max_1 ? w0 : w0_max_1; + + // Limit f0 to 4kHz to keep delta_t cycle filter stable. + const sound_sample w0_max_dt = static_cast(2*pi*4000*1.048576); + w0_ceil_dt = w0 <= w0_max_dt ? w0 : w0_max_dt; +} + +// Set filter resonance. +void Filter::set_Q() { + // Q is controlled linearly by res. Q has approximate range [0.707, 1.7]. + // As resonance is increased, the filter must be clocked more often to keep + // stable. + + // The coefficient 1024 is dispensed of later by right-shifting 10 times + // (2 ^ 10 = 1024). + _1024_div_Q = static_cast(1024.0/(0.707 + 1.0*res/0x0f)); +} + +RESID_INLINE void Filter::clock(cycle_count delta_t, + sound_sample voice1, + sound_sample voice2, + sound_sample voice3) +{ + // Scale each voice down from 20 to 13 bits. + voice1 >>= 7; + voice2 >>= 7; + + // NB! Voice 3 is not silenced by voice3off if it is routed through + // the filter. + if (voice3off && !(filt & 0x04)) { + voice3 = 0; + } + else { + voice3 >>= 7; + } + + // Enable filter on/off. + // This is not really part of SID, but is useful for testing. + // On slow CPUs it may be necessary to bypass the filter to lower the CPU + // load. + if (!enabled) { + Vnf = voice1 + voice2 + voice3; + Vhp = Vbp = Vlp = 0; + return; + } + + // Route voices into or around filter. + // The code below is expanded to a switch for faster execution. + // (filt1 ? Vi : Vnf) += voice1; + // (filt2 ? Vi : Vnf) += voice2; + // (filt3 ? Vi : Vnf) += voice3; + + sound_sample Vi; + + switch (filt) { + default: + case 0x0: + Vi = 0; + Vnf = voice1 + voice2 + voice3; + break; + case 0x1: + Vi = voice1; + Vnf = voice2 + voice3; + break; + case 0x2: + Vi = voice2; + Vnf = voice1 + voice3; + break; + case 0x3: + Vi = voice1 + voice2; + Vnf = voice3; + break; + case 0x4: + Vi = voice3; + Vnf = voice1 + voice2; + break; + case 0x5: + Vi = voice1 + voice3; + Vnf = voice2; + break; + case 0x6: + Vi = voice2 + voice3; + Vnf = voice1; + break; + case 0x7: + Vi = voice1 + voice2 + voice3; + Vnf = 0; + break; + case 0x8: + Vi = 0; + Vnf = voice1 + voice2 + voice3; + break; + case 0x9: + Vi = voice1; + Vnf = voice2 + voice3; + break; + case 0xa: + Vi = voice2; + Vnf = voice1 + voice3; + break; + case 0xb: + Vi = voice1 + voice2; + Vnf = voice3; + break; + case 0xc: + Vi = voice3; + Vnf = voice1 + voice2; + break; + case 0xd: + Vi = voice1 + voice3; + Vnf = voice2; + break; + case 0xe: + Vi = voice2 + voice3; + Vnf = voice1; + break; + case 0xf: + Vi = voice1 + voice2 + voice3; + Vnf = 0; + break; + } + + // Maximum delta cycles for the filter to work satisfactorily under current + // cutoff frequency and resonance constraints is approximately 8. + cycle_count delta_t_flt = 8; + + while (delta_t) { + if (delta_t < delta_t_flt) { + delta_t_flt = delta_t; + } + + // delta_t is converted to seconds given a 1MHz clock by dividing + // with 1 000 000. This is done in two operations to avoid integer + // multiplication overflow. + + // Calculate filter outputs. + // Vhp = Vbp/Q - Vlp - Vi; + // dVbp = -w0*Vhp*dt; + // dVlp = -w0*Vbp*dt; + sound_sample w0_delta_t = w0_ceil_dt*delta_t_flt >> 6; + + sound_sample dVbp = (w0_delta_t*Vhp >> 14); + sound_sample dVlp = (w0_delta_t*Vbp >> 14); + Vbp -= dVbp; + Vlp -= dVlp; + Vhp = (Vbp*_1024_div_Q >> 10) - Vlp - Vi; + + delta_t -= delta_t_flt; + } +} + +RESID_INLINE sound_sample Filter::output() { + // This is handy for testing. + if (!enabled) { + return (Vnf + mixer_DC)*static_cast(vol); + } + + // Mix highpass, bandpass, and lowpass outputs. The sum is not + // weighted, this can be confirmed by sampling sound output for + // e.g. bandpass, lowpass, and bandpass+lowpass from a SID chip. + + // The code below is expanded to a switch for faster execution. + // if (hp) Vf += Vhp; + // if (bp) Vf += Vbp; + // if (lp) Vf += Vlp; + + sound_sample Vf; + + switch (hp_bp_lp) { + default: + case 0x0: + Vf = 0; + break; + case 0x1: + Vf = Vlp; + break; + case 0x2: + Vf = Vbp; + break; + case 0x3: + Vf = Vlp + Vbp; + break; + case 0x4: + Vf = Vhp; + break; + case 0x5: + Vf = Vlp + Vhp; + break; + case 0x6: + Vf = Vbp + Vhp; + break; + case 0x7: + Vf = Vlp + Vbp + Vhp; + break; + } + + // Sum non-filtered and filtered output. + // Multiply the sum with volume. + return (Vnf + Vf + mixer_DC)*static_cast(vol); +} + + +/* + * EnvelopeGenerator + */ + +EnvelopeGenerator::EnvelopeGenerator() { + reset(); +} + +void EnvelopeGenerator::reset() { + envelope_counter = 0; + + attack = 0; + decay = 0; + sustain = 0; + release = 0; + + gate = 0; + + rate_counter = 0; + exponential_counter = 0; + exponential_counter_period = 1; + + state = RELEASE; + rate_period = rate_counter_period[release]; + hold_zero = true; +} + +reg16 EnvelopeGenerator::rate_counter_period[] = { + 9, // 2ms*1.0MHz/256 = 7.81 + 32, // 8ms*1.0MHz/256 = 31.25 + 63, // 16ms*1.0MHz/256 = 62.50 + 95, // 24ms*1.0MHz/256 = 93.75 + 149, // 38ms*1.0MHz/256 = 148.44 + 220, // 56ms*1.0MHz/256 = 218.75 + 267, // 68ms*1.0MHz/256 = 265.63 + 313, // 80ms*1.0MHz/256 = 312.50 + 392, // 100ms*1.0MHz/256 = 390.63 + 977, // 250ms*1.0MHz/256 = 976.56 + 1954, // 500ms*1.0MHz/256 = 1953.13 + 3126, // 800ms*1.0MHz/256 = 3125.00 + 3907, // 1 s*1.0MHz/256 = 3906.25 + 11720, // 3 s*1.0MHz/256 = 11718.75 + 19532, // 5 s*1.0MHz/256 = 19531.25 + 31251 // 8 s*1.0MHz/256 = 31250.00 +}; + + +reg8 EnvelopeGenerator::sustain_level[] = { + 0x00, + 0x11, + 0x22, + 0x33, + 0x44, + 0x55, + 0x66, + 0x77, + 0x88, + 0x99, + 0xaa, + 0xbb, + 0xcc, + 0xdd, + 0xee, + 0xff, +}; + +void EnvelopeGenerator::writeCONTROL_REG(reg8 control) { + reg8 gate_next = control & 0x01; + + // The rate counter is never reset, thus there will be a delay before the + // envelope counter starts counting up (attack) or down (release). + + // Gate bit on: Start attack, decay, sustain. + if (!gate && gate_next) { + state = ATTACK; + rate_period = rate_counter_period[attack]; + + // Switching to attack state unlocks the zero freeze. + hold_zero = false; + } + // Gate bit off: Start release. + else if (gate && !gate_next) { + state = RELEASE; + rate_period = rate_counter_period[release]; + } + + gate = gate_next; +} + +void EnvelopeGenerator::writeATTACK_DECAY(reg8 attack_decay) { + attack = (attack_decay >> 4) & 0x0f; + decay = attack_decay & 0x0f; + if (state == ATTACK) { + rate_period = rate_counter_period[attack]; + } + else if (state == DECAY_SUSTAIN) { + rate_period = rate_counter_period[decay]; + } +} + +void EnvelopeGenerator::writeSUSTAIN_RELEASE(reg8 sustain_release) { + sustain = (sustain_release >> 4) & 0x0f; + release = sustain_release & 0x0f; + if (state == RELEASE) { + rate_period = rate_counter_period[release]; + } +} + +reg8 EnvelopeGenerator::readENV() { + return output(); +} + +RESID_INLINE void EnvelopeGenerator::clock(cycle_count delta_t) { + // Check for ADSR delay bug. + // If the rate counter comparison value is set below the current value of the + // rate counter, the counter will continue counting up until it wraps around + // to zero at 2^15 = 0x8000, and then count rate_period - 1 before the + // envelope can finally be stepped. + // This has been verified by sampling ENV3. + // + + // NB! This requires two's complement integer. + int rate_step = rate_period - rate_counter; + if (rate_step <= 0) { + rate_step += 0x7fff; + } + + while (delta_t) { + if (delta_t < rate_step) { + rate_counter += delta_t; + if (rate_counter & 0x8000) { + ++rate_counter &= 0x7fff; + } + return; + } + + rate_counter = 0; + delta_t -= rate_step; + + // The first envelope step in the attack state also resets the exponential + // counter. This has been verified by sampling ENV3. + // + if (state == ATTACK || ++exponential_counter == exponential_counter_period) + { + exponential_counter = 0; + + // Check whether the envelope counter is frozen at zero. + if (hold_zero) { + rate_step = rate_period; + continue; + } + + switch (state) { + case ATTACK: + // The envelope counter can flip from 0xff to 0x00 by changing state to + // release, then to attack. The envelope counter is then frozen at + // zero; to unlock this situation the state must be changed to release, + // then to attack. This has been verified by sampling ENV3. + // + ++envelope_counter &= 0xff; + if (envelope_counter == 0xff) { + state = DECAY_SUSTAIN; + rate_period = rate_counter_period[decay]; + } + break; + case DECAY_SUSTAIN: + if (envelope_counter != sustain_level[sustain]) { + --envelope_counter; + } + break; + case RELEASE: + // The envelope counter can flip from 0x00 to 0xff by changing state to + // attack, then to release. The envelope counter will then continue + // counting down in the release state. + // This has been verified by sampling ENV3. + // NB! The operation below requires two's complement integer. + // + --envelope_counter &= 0xff; + break; + } + + // Check for change of exponential counter period. + switch (envelope_counter) { + case 0xff: + exponential_counter_period = 1; + break; + case 0x5d: + exponential_counter_period = 2; + break; + case 0x36: + exponential_counter_period = 4; + break; + case 0x1a: + exponential_counter_period = 8; + break; + case 0x0e: + exponential_counter_period = 16; + break; + case 0x06: + exponential_counter_period = 30; + break; + case 0x00: + exponential_counter_period = 1; + + // When the envelope counter is changed to zero, it is frozen at zero. + // This has been verified by sampling ENV3. + hold_zero = true; + break; + } + } + + rate_step = rate_period; + } +} + +RESID_INLINE reg8 EnvelopeGenerator::output() { + return envelope_counter; +} + + +/* + * ExternalFilter + */ + +ExternalFilter::ExternalFilter() { + reset(); + enable_filter(true); + set_sampling_parameter(15915.6); + mixer_DC = ((((0x800 - 0x380) + 0x800)*0xff*3 - 0xfff*0xff/18) >> 7)*0x0f; +} + +void ExternalFilter::enable_filter(bool enable) { + enabled = enable; +} + +void ExternalFilter::set_sampling_parameter(double pass_freq) { + static const double pi = 3.1415926535897932385; + + w0hp = 105; + w0lp = (sound_sample) (pass_freq * (2.0 * pi * 1.048576)); + if (w0lp > 104858) + w0lp = 104858; +} + +void ExternalFilter::reset() { + // State of filter. + Vlp = 0; + Vhp = 0; + Vo = 0; +} + +RESID_INLINE void ExternalFilter::clock(cycle_count delta_t, sound_sample Vi) { + // This is handy for testing. + if (!enabled) { + // Remove maximum DC level since there is no filter to do it. + Vlp = Vhp = 0; + Vo = Vi - mixer_DC; + return; + } + + // Maximum delta cycles for the external filter to work satisfactorily + // is approximately 8. + cycle_count delta_t_flt = 8; + + while (delta_t) { + if (delta_t < delta_t_flt) { + delta_t_flt = delta_t; + } + + // delta_t is converted to seconds given a 1MHz clock by dividing + // with 1 000 000. + + // Calculate filter outputs. + // Vo = Vlp - Vhp; + // Vlp = Vlp + w0lp*(Vi - Vlp)*delta_t; + // Vhp = Vhp + w0hp*(Vlp - Vhp)*delta_t; + + sound_sample dVlp = (w0lp*delta_t_flt >> 8)*(Vi - Vlp) >> 12; + sound_sample dVhp = w0hp*delta_t_flt*(Vlp - Vhp) >> 20; + Vo = Vlp - Vhp; + Vlp += dVlp; + Vhp += dVhp; + + delta_t -= delta_t_flt; + } +} + +RESID_INLINE sound_sample ExternalFilter::output() { + return Vo; +} + + +/* + * Voice + */ + +Voice::Voice() { + wave_zero = 0x380; + voice_DC = 0x800*0xff; +} + +void Voice::set_sync_source(Voice* source) { + wave.set_sync_source(&source->wave); +} + +void Voice::writeCONTROL_REG(reg8 control) { + wave.writeCONTROL_REG(control); + envelope.writeCONTROL_REG(control); +} + +void Voice::reset() { + wave.reset(); + envelope.reset(); +} + + +/* + * SID + */ + +SID::SID() { + voice[0].set_sync_source(&voice[2]); + voice[1].set_sync_source(&voice[0]); + voice[2].set_sync_source(&voice[1]); + + set_sampling_parameters(985248, 44100); + + bus_value = 0; + bus_value_ttl = 0; +} + +SID::~SID() {} + +void SID::reset() { + for (int i = 0; i < 3; i++) { + voice[i].reset(); + } + filter.reset(); + extfilt.reset(); + + bus_value = 0; + bus_value_ttl = 0; +} + +int SID::output() { + const int range = 1 << 16; + const int half = range >> 1; + int sample = extfilt.output()/((4095*255 >> 7)*3*15*2/range); + if (sample >= half) { + return half - 1; + } + if (sample < -half) { + return -half; + } + return sample; +} + + +/** + * Read registers. + * + * Reading a write only register returns the last byte written to any SID + * register. The individual bits in this value start to fade down towards + * zero after a few cycles. All bits reach zero within approximately + * $2000 - $4000 cycles. + * It has been claimed that this fading happens in an orderly fashion, however + * sampling of write only registers reveals that this is not the case. + * NB! This is not correctly modeled. + * The actual use of write only registers has largely been made in the belief + * that all SID registers are readable. To support this belief the read + * would have to be done immediately after a write to the same register + * (remember that an intermediate write to another register would yield that + * value instead). With this in mind we return the last value written to + * any SID register for $2000 cycles without modeling the bit fading. + */ +reg8 SID::read(reg8 offset) { + switch (offset) { + case 0x19: + case 0x1a: + return 0; //readPOT(); + case 0x1b: + return voice[2].wave.readOSC(); + case 0x1c: + return voice[2].envelope.readENV(); + default: + return bus_value; + } +} + +void SID::write(reg8 offset, reg8 value) { + bus_value = value; + bus_value_ttl = 0x2000; + + switch (offset) { + case 0x00: + voice[0].wave.writeFREQ_LO(value); + break; + case 0x01: + voice[0].wave.writeFREQ_HI(value); + break; + case 0x02: + voice[0].wave.writePW_LO(value); + break; + case 0x03: + voice[0].wave.writePW_HI(value); + break; + case 0x04: + voice[0].writeCONTROL_REG(value); + break; + case 0x05: + voice[0].envelope.writeATTACK_DECAY(value); + break; + case 0x06: + voice[0].envelope.writeSUSTAIN_RELEASE(value); + break; + case 0x07: + voice[1].wave.writeFREQ_LO(value); + break; + case 0x08: + voice[1].wave.writeFREQ_HI(value); + break; + case 0x09: + voice[1].wave.writePW_LO(value); + break; + case 0x0a: + voice[1].wave.writePW_HI(value); + break; + case 0x0b: + voice[1].writeCONTROL_REG(value); + break; + case 0x0c: + voice[1].envelope.writeATTACK_DECAY(value); + break; + case 0x0d: + voice[1].envelope.writeSUSTAIN_RELEASE(value); + break; + case 0x0e: + voice[2].wave.writeFREQ_LO(value); + break; + case 0x0f: + voice[2].wave.writeFREQ_HI(value); + break; + case 0x10: + voice[2].wave.writePW_LO(value); + break; + case 0x11: + voice[2].wave.writePW_HI(value); + break; + case 0x12: + voice[2].writeCONTROL_REG(value); + break; + case 0x13: + voice[2].envelope.writeATTACK_DECAY(value); + break; + case 0x14: + voice[2].envelope.writeSUSTAIN_RELEASE(value); + break; + case 0x15: + filter.writeFC_LO(value); + break; + case 0x16: + filter.writeFC_HI(value); + break; + case 0x17: + filter.writeRES_FILT(value); + break; + case 0x18: + filter.writeMODE_VOL(value); + break; + default: + break; + } +} + +void SID::enable_filter(bool enable) { + filter.enable_filter(enable); +} + +void SID::enable_external_filter(bool enable) { + extfilt.enable_filter(enable); +} + + +/** + * Setting of SID sampling parameters. + * + * Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64. + * The default end of passband frequency is pass_freq = 0.9*sample_freq/2 + * for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample + * frequencies. + * + * For resampling, the ratio between the clock frequency and the sample + * frequency is limited as follows: + * 125*clock_freq/sample_freq < 16384 + * E.g. provided a clock frequency of ~ 1MHz, the sample frequency can not + * be set lower than ~ 8kHz. A lower sample frequency would make the + * resampling code overfill its 16k sample ring buffer. + * + * The end of passband frequency is also limited: + * pass_freq <= 0.9*sample_freq/2 + * + * E.g. for a 44.1kHz sampling rate the end of passband frequency is limited + * to slightly below 20kHz. This constraint ensures that the FIR table is + * not overfilled. + */ +bool SID::set_sampling_parameters(double clock_freq, + double sample_freq, double pass_freq, + double filter_scale) +{ + // The default passband limit is 0.9*sample_freq/2 for sample + // frequencies below ~ 44.1kHz, and 20kHz for higher sample frequencies. + if (pass_freq < 0) { + pass_freq = 20000; + if (2*pass_freq/sample_freq >= 0.9) { + pass_freq = 0.9*sample_freq/2; + } + } + // Check whether the FIR table would overfill. + else if (pass_freq > 0.9*sample_freq/2) { + return false; + } + + // The filter scaling is only included to avoid clipping, so keep + // it sane. + if (filter_scale < 0.9 || filter_scale > 1.0) { + return false; + } + + // Set the external filter to the pass freq + extfilt.set_sampling_parameter (pass_freq); + clock_frequency = clock_freq; + + cycles_per_sample = + cycle_count(clock_freq/sample_freq*(1 << FIXP_SHIFT) + 0.5); + + sample_offset = 0; + sample_prev = 0; + + return true; +} + +void SID::clock(cycle_count delta_t) { + int i; + + if (delta_t <= 0) { + return; + } + + // Age bus value. + bus_value_ttl -= delta_t; + if (bus_value_ttl <= 0) { + bus_value = 0; + bus_value_ttl = 0; + } + + // Clock amplitude modulators. + for (i = 0; i < 3; i++) { + voice[i].envelope.clock(delta_t); + } + + // Clock and synchronize oscillators. + // Loop until we reach the current cycle. + cycle_count delta_t_osc = delta_t; + while (delta_t_osc) { + cycle_count delta_t_min = delta_t_osc; + + // Find minimum number of cycles to an oscillator accumulator MSB toggle. + // We have to clock on each MSB on / MSB off for hard sync to operate + // correctly. + for (i = 0; i < 3; i++) { + WaveformGenerator& wave = voice[i].wave; + + // It is only necessary to clock on the MSB of an oscillator that is + // a sync source and has freq != 0. + if (!(wave.sync_dest->sync && wave.freq)) { + continue; + } + + reg16 freq = wave.freq; + reg24 accumulator = wave.accumulator; + + // Clock on MSB off if MSB is on, clock on MSB on if MSB is off. + reg24 delta_accumulator = + (accumulator & 0x800000 ? 0x1000000 : 0x800000) - accumulator; + + cycle_count delta_t_next = delta_accumulator/freq; + if (delta_accumulator%freq) { + ++delta_t_next; + } + + if (delta_t_next < delta_t_min) { + delta_t_min = delta_t_next; + } + } + + // Clock oscillators. + for (i = 0; i < 3; i++) { + voice[i].wave.clock(delta_t_min); + } + + // Synchronize oscillators. + for (i = 0; i < 3; i++) { + voice[i].wave.synchronize(); + } + + delta_t_osc -= delta_t_min; + } + + // Clock filter. + filter.clock(delta_t, + voice[0].output(), voice[1].output(), voice[2].output()); + + // Clock external filter. + extfilt.clock(delta_t, filter.output()); +} + + +/** + * SID clocking with audio sampling. + * Fixpoint arithmetics is used. + */ +int SID::clock(cycle_count& delta_t, short* buf, int n, int interleave) { + int s = 0; + + for (;;) { + cycle_count next_sample_offset = sample_offset + cycles_per_sample + (1 << (FIXP_SHIFT - 1)); + cycle_count delta_t_sample = next_sample_offset >> FIXP_SHIFT; + if (delta_t_sample > delta_t) { + break; + } + if (s >= n) { + return s; + } + clock(delta_t_sample); + delta_t -= delta_t_sample; + sample_offset = (next_sample_offset & FIXP_MASK) - (1 << (FIXP_SHIFT - 1)); + buf[s++*interleave] = output(); + } + + clock(delta_t); + sample_offset -= delta_t << FIXP_SHIFT; + delta_t = 0; + return s; +} + +} + +// Plugin interface +// (This can only create a null driver since C64 audio support is not part of the +// midi driver architecture. But we need the plugin for the options menu in the launcher +// and for MidiDriver::detectDevice() which is more or less used by all engines.) + +class C64MusicPlugin : public NullMusicPlugin { +public: + const char *getName() const { + return _s("C64 Audio Emulator"); + } + + const char *getId() const { + return "C64"; + } + + MusicDevices getDevices() const; +}; + +MusicDevices C64MusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_C64)); + return devices; +} + +//#if PLUGIN_ENABLED_DYNAMIC(C64) + //REGISTER_PLUGIN_DYNAMIC(C64, PLUGIN_TYPE_MUSIC, C64MusicPlugin); +//#else + REGISTER_PLUGIN_STATIC(C64, PLUGIN_TYPE_MUSIC, C64MusicPlugin); +//#endif + +#endif diff --git a/audio/softsynth/sid.h b/audio/softsynth/sid.h new file mode 100644 index 0000000000..c78f538441 --- /dev/null +++ b/audio/softsynth/sid.h @@ -0,0 +1,348 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This file is based on reSID, a MOS6581 SID emulator engine. + * Copyright (C) 2004 Dag Lem + */ + +#ifndef __SID_H__ +#define __SID_H__ + +// Inlining on/off. +#define RESID_INLINE inline + +namespace Resid { + +// We could have used the smallest possible data type for each SID register, +// however this would give a slower engine because of data type conversions. +// An int is assumed to be at least 32 bits (necessary in the types reg24, +// cycle_count, and sound_sample). GNU does not support 16-bit machines +// (GNU Coding Standards: Portability between CPUs), so this should be +// a valid assumption. + +typedef unsigned int reg4; +typedef unsigned int reg8; +typedef unsigned int reg12; +typedef unsigned int reg16; +typedef unsigned int reg24; + +typedef int cycle_count; +typedef int sound_sample; +typedef sound_sample fc_point[2]; + + +class WaveformGenerator { +public: + WaveformGenerator(); + + void set_sync_source(WaveformGenerator*); + + void clock(cycle_count delta_t); + void synchronize(); + void reset(); + + void writeFREQ_LO(reg8); + void writeFREQ_HI(reg8); + void writePW_LO(reg8); + void writePW_HI(reg8); + void writeCONTROL_REG(reg8); + reg8 readOSC(); + + // 12-bit waveform output. + reg12 output(); + +protected: + const WaveformGenerator* sync_source; + WaveformGenerator* sync_dest; + + // Tell whether the accumulator MSB was set high on this cycle. + bool msb_rising; + + reg24 accumulator; + reg24 shift_register; + + // Fout = (Fn*Fclk/16777216)Hz + reg16 freq; + // PWout = (PWn/40.95)% + reg12 pw; + + // The control register right-shifted 4 bits; used for output function + // table lookup. + reg8 waveform; + + // The remaining control register bits. + reg8 test; + reg8 ring_mod; + reg8 sync; + // The gate bit is handled by the EnvelopeGenerator. + + // 16 possible combinations of waveforms. + reg12 output____(); + reg12 output___T(); + reg12 output__S_(); + reg12 output__ST(); + reg12 output_P__(); + reg12 output_P_T(); + reg12 output_PS_(); + reg12 output_PST(); + reg12 outputN___(); + reg12 outputN__T(); + reg12 outputN_S_(); + reg12 outputN_ST(); + reg12 outputNP__(); + reg12 outputNP_T(); + reg12 outputNPS_(); + reg12 outputNPST(); + + // Sample data for combinations of waveforms. + static const reg8 wave6581__ST[]; + static const reg8 wave6581_P_T[]; + static const reg8 wave6581_PS_[]; + static const reg8 wave6581_PST[]; + + friend class Voice; + friend class SID; +}; + +class Filter { +public: + Filter(); + + void enable_filter(bool enable); + + void clock(cycle_count delta_t, + sound_sample voice1, sound_sample voice2, sound_sample voice3); + void reset(); + + // Write registers. + void writeFC_LO(reg8); + void writeFC_HI(reg8); + void writeRES_FILT(reg8); + void writeMODE_VOL(reg8); + + // SID audio output (16 bits). + sound_sample output(); + +protected: + void set_w0(); + void set_Q(); + + // Filter enabled. + bool enabled; + + // Filter cutoff frequency. + reg12 fc; + + // Filter resonance. + reg8 res; + + // Selects which inputs to route through filter. + reg8 filt; + + // Switch voice 3 off. + reg8 voice3off; + + // Highpass, bandpass, and lowpass filter modes. + reg8 hp_bp_lp; + + // Output master volume. + reg4 vol; + + // Mixer DC offset. + sound_sample mixer_DC; + + // State of filter. + sound_sample Vhp; // highpass + sound_sample Vbp; // bandpass + sound_sample Vlp; // lowpass + sound_sample Vnf; // not filtered + + // Cutoff frequency, resonance. + sound_sample w0, w0_ceil_1, w0_ceil_dt; + sound_sample _1024_div_Q; + + // Cutoff frequency tables. + // FC is an 11 bit register. + sound_sample f0_6581[2048]; + sound_sample* f0; + static fc_point f0_points_6581[]; + fc_point* f0_points; + int f0_count; + + friend class SID; +}; + +class EnvelopeGenerator { +public: + EnvelopeGenerator(); + + enum State { ATTACK, DECAY_SUSTAIN, RELEASE }; + + void clock(cycle_count delta_t); + void reset(); + + void writeCONTROL_REG(reg8); + void writeATTACK_DECAY(reg8); + void writeSUSTAIN_RELEASE(reg8); + reg8 readENV(); + + // 8-bit envelope output. + reg8 output(); + +protected: + reg16 rate_counter; + reg16 rate_period; + reg8 exponential_counter; + reg8 exponential_counter_period; + reg8 envelope_counter; + bool hold_zero; + + reg4 attack; + reg4 decay; + reg4 sustain; + reg4 release; + + reg8 gate; + + State state; + + // Lookup table to convert from attack, decay, or release value to rate + // counter period. + static reg16 rate_counter_period[]; + + // The 16 selectable sustain levels. + static reg8 sustain_level[]; + + friend class SID; +}; + +class ExternalFilter { +public: + ExternalFilter(); + + void enable_filter(bool enable); + void set_sampling_parameter(double pass_freq); + + void clock(cycle_count delta_t, sound_sample Vi); + void reset(); + + // Audio output (20 bits). + sound_sample output(); + +protected: + // Filter enabled. + bool enabled; + + // Maximum mixer DC offset. + sound_sample mixer_DC; + + // State of filters. + sound_sample Vlp; // lowpass + sound_sample Vhp; // highpass + sound_sample Vo; + + // Cutoff frequencies. + sound_sample w0lp; + sound_sample w0hp; + + friend class SID; +}; + +class Voice { +public: + Voice(); + + void set_sync_source(Voice*); + void reset(); + + void writeCONTROL_REG(reg8); + + // Amplitude modulated waveform output. + // Range [-2048*255, 2047*255]. + sound_sample output() { + // Multiply oscillator output with envelope output. + return (wave.output() - wave_zero)*envelope.output() + voice_DC; + } + +protected: + WaveformGenerator wave; + EnvelopeGenerator envelope; + + // Waveform D/A zero level. + sound_sample wave_zero; + + // Multiplying D/A DC offset. + sound_sample voice_DC; + + friend class SID; +}; + + +class SID { +public: + SID(); + ~SID(); + + void enable_filter(bool enable); + void enable_external_filter(bool enable); + bool set_sampling_parameters(double clock_freq, + double sample_freq, double pass_freq = -1, + double filter_scale = 0.97); + + void clock(cycle_count delta_t); + int clock(cycle_count& delta_t, short* buf, int n, int interleave = 1); + void reset(); + + // Read/write registers. + reg8 read(reg8 offset); + void write(reg8 offset, reg8 value); + + // 16-bit output (AUDIO OUT). + int output(); + +protected: + Voice voice[3]; + Filter filter; + ExternalFilter extfilt; + + reg8 bus_value; + cycle_count bus_value_ttl; + + double clock_frequency; + + // Fixpoint constants. + static const int FIXP_SHIFT; + static const int FIXP_MASK; + + // Sampling variables. + cycle_count cycles_per_sample; + cycle_count sample_offset; + short sample_prev; +}; + +} + +#endif // not __SID_H__ diff --git a/audio/softsynth/wave6581.cpp b/audio/softsynth/wave6581.cpp new file mode 100644 index 0000000000..d1ddad1623 --- /dev/null +++ b/audio/softsynth/wave6581.cpp @@ -0,0 +1,2098 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This file is based on reSID, a MOS6581 SID emulator engine. + * Copyright (C) 2004 Dag Lem + */ + +#ifndef DISABLE_SID + +#include "sid.h" + +namespace Resid { + +const reg8 WaveformGenerator::wave6581__ST[] = { +/* 0x000: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x008: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x010: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x018: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x020: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x028: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x030: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x038: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x040: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x048: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x050: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x058: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x060: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x068: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x070: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x078: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0x080: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x088: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x090: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x098: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x0c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, +/* 0x100: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x108: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x110: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x118: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x120: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x128: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x130: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x138: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x140: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x148: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x150: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x158: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x160: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x168: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x170: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x178: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0x180: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x188: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x190: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x198: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x1c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1f8: */ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, +/* 0x200: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x208: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x210: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x218: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x220: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x228: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x230: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x238: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x240: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x248: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x250: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x258: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x260: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x268: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x270: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x278: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0x280: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x288: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x290: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x298: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x2c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, +/* 0x300: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x308: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x310: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x318: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x320: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x328: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x330: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x338: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x340: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x348: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x350: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x358: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x360: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x368: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x370: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x378: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0x380: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x388: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x390: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x398: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x3c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3f0: */ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, +/* 0x3f8: */ 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x3f, 0x3f, +/* 0x400: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x408: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x410: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x418: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x420: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x428: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x430: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x438: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x440: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x448: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x450: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x458: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x460: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x468: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x470: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x478: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0x480: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x488: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x490: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x498: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x4c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, +/* 0x500: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x508: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x510: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x518: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x520: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x528: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x530: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x538: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x540: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x548: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x550: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x558: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x560: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x568: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x570: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x578: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0x580: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x588: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x590: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x598: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x5c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5f8: */ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x1f, +/* 0x600: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x608: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x610: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x618: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x620: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x628: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x630: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x638: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x640: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x648: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x650: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x658: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x660: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x668: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x670: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x678: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0x680: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x688: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x690: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x698: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x6c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, +/* 0x700: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x708: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x710: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x718: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x720: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x728: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x730: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x738: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x740: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x748: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x750: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x758: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x760: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x768: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x770: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x778: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0x780: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x788: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x790: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x798: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x7c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7e0: */ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, +/* 0x7e8: */ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, +/* 0x7f0: */ 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, +/* 0x7f8: */ 0x3e, 0x3e, 0x3f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f, +/* 0x800: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x808: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x810: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x818: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x820: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x828: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x830: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x838: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x840: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x848: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x850: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x858: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x860: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x868: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x870: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x878: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0x880: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x888: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x890: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x898: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x8c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, +/* 0x900: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x908: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x910: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x918: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x920: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x928: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x930: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x938: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x940: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x948: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x950: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x958: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x960: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x968: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x970: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x978: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0x980: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x988: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x990: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x998: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x9c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9f8: */ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, +/* 0xa00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0xa80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xab0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xab8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0xac0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xac8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xad0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xad8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xae0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xae8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaf8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, +/* 0xb00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0xb40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0xb80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xba0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xba8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0xbc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbf0: */ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, +/* 0xbf8: */ 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x3f, 0x3f, +/* 0xc00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0xc80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xca0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xca8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0xcc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xce0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xce8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcf8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, +/* 0xd00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0xd40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0xd80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xda0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xda8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0xdc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xde0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xde8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdf8: */ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x1f, +/* 0xe00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0xe80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xea0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xea8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xeb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xeb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0xec0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xec8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xed0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xed8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xee0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xee8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xef0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xef8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, +/* 0xf00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0xf40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, +/* 0xf80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0xfc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfe0: */ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, +/* 0xfe8: */ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, +/* 0xff0: */ 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, +/* 0xff8: */ 0x3e, 0x3e, 0x3f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f, +}; + +const reg8 WaveformGenerator::wave6581_P_T[] = { +/* 0x000: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x008: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x010: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x018: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x020: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x028: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x030: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x038: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x040: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x048: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x050: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x058: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x060: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x068: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x070: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x078: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x080: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x088: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x090: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x098: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x100: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x108: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x110: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x118: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x120: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x128: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x130: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x138: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x140: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x148: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x150: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x158: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x160: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x168: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x170: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x178: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x180: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x188: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x190: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x198: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x38, 0x3f, +/* 0x200: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x208: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x210: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x218: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x220: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x228: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x230: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x238: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x240: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x248: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x250: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x258: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x260: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x268: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x270: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x278: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x280: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x288: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x290: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x298: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2f8: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x5f, +/* 0x300: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x308: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x310: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x318: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x320: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x328: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x330: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x338: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x340: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x348: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x350: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x358: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x360: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x368: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0x370: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0x378: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x6f, +/* 0x380: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x388: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x390: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x398: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0x3a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, +/* 0x3b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, +/* 0x3b8: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x70, 0x77, +/* 0x3c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, +/* 0x3d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, +/* 0x3d8: */ 0x00, 0x00, 0x00, 0x70, 0x40, 0x70, 0x70, 0x7b, +/* 0x3e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x70, +/* 0x3e8: */ 0x00, 0x40, 0x40, 0x70, 0x60, 0x70, 0x78, 0x7d, +/* 0x3f0: */ 0x00, 0x40, 0x60, 0x78, 0x60, 0x78, 0x78, 0x7e, +/* 0x3f8: */ 0x70, 0x7c, 0x7c, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, +/* 0x400: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x408: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x410: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x418: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x420: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x428: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x430: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x438: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x440: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x448: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x450: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x458: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x460: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x468: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x470: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x478: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x480: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x488: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x490: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x498: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x4c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x4e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x4f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x4f8: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x9f, +/* 0x500: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x508: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x510: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x518: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x520: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x528: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x530: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x538: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x540: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x548: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x550: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x558: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x560: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x568: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x570: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x578: */ 0x00, 0x80, 0x80, 0x80, 0x80, 0xa0, 0xa0, 0xaf, +/* 0x580: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x588: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x590: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x598: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x5a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, +/* 0x5b0: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xa0, +/* 0x5b8: */ 0x00, 0x80, 0x80, 0xa0, 0x80, 0xa0, 0xb0, 0xb7, +/* 0x5c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x5c8: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xa0, +/* 0x5d0: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xa0, +/* 0x5d8: */ 0x00, 0x80, 0x80, 0xa0, 0x80, 0xb0, 0xb0, 0xbb, +/* 0x5e0: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0xb0, +/* 0x5e8: */ 0x80, 0x80, 0x80, 0xb0, 0x80, 0xb0, 0xb8, 0xbd, +/* 0x5f0: */ 0x80, 0x80, 0x80, 0xb8, 0xa0, 0xb8, 0xb8, 0xbe, +/* 0x5f8: */ 0xa0, 0xb8, 0xbc, 0xbf, 0xbe, 0xbf, 0xbf, 0xbf, +/* 0x600: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x608: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x610: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x618: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x620: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x628: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x630: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x638: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, +/* 0x640: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x648: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x650: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x658: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0xc0, +/* 0x660: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x668: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, +/* 0x670: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, +/* 0x678: */ 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xcf, +/* 0x680: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x688: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x690: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x698: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, +/* 0x6a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x6a8: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, +/* 0x6b0: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0xc0, 0xc0, +/* 0x6b8: */ 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xd0, 0xd7, +/* 0x6c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x6c8: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0xc0, 0xc0, +/* 0x6d0: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xc0, +/* 0x6d8: */ 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xd0, 0xd0, 0xdb, +/* 0x6e0: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xd0, +/* 0x6e8: */ 0x80, 0xc0, 0xc0, 0xd0, 0xc0, 0xd0, 0xd8, 0xdd, +/* 0x6f0: */ 0xc0, 0xc0, 0xc0, 0xd0, 0xc0, 0xd8, 0xd8, 0xde, +/* 0x6f8: */ 0xc0, 0xd8, 0xdc, 0xdf, 0xdc, 0xdf, 0xdf, 0xdf, +/* 0x700: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x708: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x710: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x718: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xe0, +/* 0x720: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +/* 0x728: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xe0, +/* 0x730: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xe0, +/* 0x738: */ 0x80, 0xc0, 0xc0, 0xe0, 0xc0, 0xe0, 0xe0, 0xe7, +/* 0x740: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, +/* 0x748: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xe0, +/* 0x750: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xe0, +/* 0x758: */ 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xeb, +/* 0x760: */ 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, +/* 0x768: */ 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xed, +/* 0x770: */ 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe8, 0xe8, 0xee, +/* 0x778: */ 0xe0, 0xe8, 0xec, 0xef, 0xec, 0xef, 0xef, 0xef, +/* 0x780: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0xc0, +/* 0x788: */ 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xf0, +/* 0x790: */ 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xf0, +/* 0x798: */ 0xc0, 0xe0, 0xe0, 0xf0, 0xe0, 0xf0, 0xf0, 0xf3, +/* 0x7a0: */ 0x80, 0xc0, 0xc0, 0xe0, 0xc0, 0xe0, 0xe0, 0xf0, +/* 0x7a8: */ 0xc0, 0xe0, 0xe0, 0xf0, 0xe0, 0xf0, 0xf0, 0xf5, +/* 0x7b0: */ 0xe0, 0xe0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf6, +/* 0x7b8: */ 0xf0, 0xf0, 0xf4, 0xf7, 0xf4, 0xf7, 0xf7, 0xf7, +/* 0x7c0: */ 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xf0, +/* 0x7c8: */ 0xe0, 0xe0, 0xe0, 0xf8, 0xf0, 0xf8, 0xf8, 0xf9, +/* 0x7d0: */ 0xe0, 0xf0, 0xf0, 0xf8, 0xf0, 0xf8, 0xf8, 0xfa, +/* 0x7d8: */ 0xf0, 0xf8, 0xf8, 0xfb, 0xf8, 0xfb, 0xfb, 0xfb, +/* 0x7e0: */ 0xe0, 0xf0, 0xf0, 0xf8, 0xf0, 0xf8, 0xfc, 0xfc, +/* 0x7e8: */ 0xf8, 0xfc, 0xfc, 0xfd, 0xfc, 0xfd, 0xfd, 0xfd, +/* 0x7f0: */ 0xf8, 0xfc, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, +/* 0x7f8: */ 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +/* 0x800: */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, +/* 0x808: */ 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfc, 0xf8, +/* 0x810: */ 0xfd, 0xfd, 0xfd, 0xfc, 0xfd, 0xfc, 0xfc, 0xf8, +/* 0x818: */ 0xfc, 0xfc, 0xfc, 0xf0, 0xf8, 0xf0, 0xf0, 0xe0, +/* 0x820: */ 0xfb, 0xfb, 0xfb, 0xf8, 0xfb, 0xf8, 0xf8, 0xf0, +/* 0x828: */ 0xfa, 0xf8, 0xf8, 0xf0, 0xf8, 0xf0, 0xf0, 0xe0, +/* 0x830: */ 0xf9, 0xf8, 0xf8, 0xf0, 0xf8, 0xf0, 0xe0, 0xe0, +/* 0x838: */ 0xf0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, +/* 0x840: */ 0xf7, 0xf7, 0xf7, 0xf4, 0xf7, 0xf4, 0xf0, 0xf0, +/* 0x848: */ 0xf6, 0xf0, 0xf0, 0xf0, 0xf0, 0xe0, 0xe0, 0xe0, +/* 0x850: */ 0xf5, 0xf0, 0xf0, 0xe0, 0xf0, 0xe0, 0xe0, 0xc0, +/* 0x858: */ 0xf0, 0xe0, 0xe0, 0xc0, 0xe0, 0xc0, 0xc0, 0x80, +/* 0x860: */ 0xf3, 0xf0, 0xf0, 0xe0, 0xf0, 0xe0, 0xe0, 0xc0, +/* 0x868: */ 0xf0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, +/* 0x870: */ 0xf0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, 0x80, +/* 0x878: */ 0xc0, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, +/* 0x880: */ 0xef, 0xef, 0xef, 0xec, 0xef, 0xec, 0xe8, 0xe0, +/* 0x888: */ 0xee, 0xe8, 0xe8, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, +/* 0x890: */ 0xed, 0xe8, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, +/* 0x898: */ 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, 0x80, +/* 0x8a0: */ 0xeb, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, +/* 0x8a8: */ 0xe0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00, +/* 0x8b0: */ 0xe0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00, +/* 0x8b8: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, +/* 0x8c0: */ 0xe7, 0xe0, 0xe0, 0xc0, 0xe0, 0xc0, 0xc0, 0x80, +/* 0x8c8: */ 0xe0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00, +/* 0x8d0: */ 0xe0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00, +/* 0x8d8: */ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8e0: */ 0xe0, 0xc0, 0xc0, 0x80, 0x80, 0x00, 0x00, 0x00, +/* 0x8e8: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8f0: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x900: */ 0xdf, 0xdf, 0xdf, 0xdc, 0xdf, 0xdc, 0xd8, 0xc0, +/* 0x908: */ 0xde, 0xd8, 0xd8, 0xc0, 0xd8, 0xc0, 0xc0, 0xc0, +/* 0x910: */ 0xdd, 0xd8, 0xd0, 0xc0, 0xd0, 0xc0, 0xc0, 0x80, +/* 0x918: */ 0xd0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00, +/* 0x920: */ 0xdb, 0xd0, 0xd0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, +/* 0x928: */ 0xc0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00, +/* 0x930: */ 0xc0, 0xc0, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, +/* 0x938: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x940: */ 0xd7, 0xd0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, +/* 0x948: */ 0xc0, 0xc0, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, +/* 0x950: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, +/* 0x958: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x960: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, +/* 0x968: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x970: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x978: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x980: */ 0xcf, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, 0x80, 0x00, +/* 0x988: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, +/* 0x990: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, +/* 0x998: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9a0: */ 0xc0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9c0: */ 0xc0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa00: */ 0xbf, 0xbf, 0xbf, 0xbe, 0xbf, 0xbc, 0xbc, 0xa0, +/* 0xa08: */ 0xbe, 0xbc, 0xb8, 0xa0, 0xb8, 0xa0, 0x80, 0x80, +/* 0xa10: */ 0xbd, 0xb8, 0xb0, 0x80, 0xb0, 0x80, 0x80, 0x80, +/* 0xa18: */ 0xb0, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, +/* 0xa20: */ 0xbb, 0xb0, 0xb0, 0x80, 0xa0, 0x80, 0x80, 0x00, +/* 0xa28: */ 0xa0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, +/* 0xa30: */ 0xa0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, +/* 0xa38: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa40: */ 0xb7, 0xb0, 0xa0, 0x80, 0xa0, 0x80, 0x80, 0x00, +/* 0xa48: */ 0xa0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, +/* 0xa50: */ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa60: */ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa80: */ 0xaf, 0xa0, 0xa0, 0x80, 0x80, 0x80, 0x80, 0x00, +/* 0xa88: */ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa90: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaa0: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xab0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xab8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xac0: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xac8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xad0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xad8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xae0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xae8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb00: */ 0x9f, 0x90, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, +/* 0xb08: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb10: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb20: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb40: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb80: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xba0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xba8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc00: */ 0x7f, 0x7f, 0x7f, 0x7e, 0x7f, 0x7c, 0x7c, 0x70, +/* 0xc08: */ 0x7e, 0x7c, 0x78, 0x60, 0x78, 0x60, 0x60, 0x00, +/* 0xc10: */ 0x7d, 0x78, 0x78, 0x60, 0x70, 0x40, 0x40, 0x00, +/* 0xc18: */ 0x70, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc20: */ 0x7b, 0x78, 0x70, 0x40, 0x70, 0x40, 0x00, 0x00, +/* 0xc28: */ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc30: */ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc40: */ 0x77, 0x70, 0x70, 0x00, 0x60, 0x00, 0x00, 0x00, +/* 0xc48: */ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc50: */ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc60: */ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc80: */ 0x6f, 0x60, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, +/* 0xc88: */ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc90: */ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xca0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xca8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xce0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xce8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd00: */ 0x5f, 0x58, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, +/* 0xd08: */ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xda0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xda8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xde0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xde8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe00: */ 0x3f, 0x3c, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xea0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xea8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xeb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xeb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xec0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xec8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xed0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xed8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xee0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xee8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xef0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xef8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xff0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xff8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +const reg8 WaveformGenerator::wave6581_PS_[] = { +/* 0x000: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x008: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x010: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x018: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x020: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x028: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x030: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x038: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x040: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x048: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x050: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x058: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x060: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x068: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x070: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x078: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x080: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x088: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x090: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x098: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, +/* 0x100: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x108: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x110: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x118: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x120: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x128: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x130: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x138: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x140: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x148: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x150: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x158: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x160: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x168: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x170: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x178: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, +/* 0x180: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x188: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x190: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x198: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, +/* 0x1c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1f, +/* 0x200: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x208: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x210: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x218: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x220: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x228: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x230: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x238: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x240: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x248: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x250: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x258: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x260: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x268: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x270: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x278: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, +/* 0x280: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x288: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x290: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x298: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0x2c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, +/* 0x300: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x308: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x310: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x318: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x320: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x328: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x330: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x338: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x340: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x348: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x350: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x358: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x360: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x368: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x370: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x378: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, +/* 0x380: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x388: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x390: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x398: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, +/* 0x3c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, +/* 0x3e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, +/* 0x3f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x3f, +/* 0x3f8: */ 0x00, 0x30, 0x38, 0x3f, 0x3e, 0x3f, 0x3f, 0x3f, +/* 0x400: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x408: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x410: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x418: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x420: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x428: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x430: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x438: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x440: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x448: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x450: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x458: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x460: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x468: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x470: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x478: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, +/* 0x480: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x488: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x490: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x498: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, +/* 0x500: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x508: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x510: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x518: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x520: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x528: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x530: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x538: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x540: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x548: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x550: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x558: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x560: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x568: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x570: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x578: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, +/* 0x580: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x588: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x590: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x598: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, +/* 0x5c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, +/* 0x5e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, +/* 0x5f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x5f, +/* 0x5f8: */ 0x00, 0x40, 0x40, 0x5f, 0x5c, 0x5f, 0x5f, 0x5f, +/* 0x600: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x608: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x610: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x618: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x620: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x628: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x630: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x638: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x640: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x648: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x650: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x658: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x660: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x668: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x670: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x678: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, +/* 0x680: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x688: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x690: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x698: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6b, +/* 0x6c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x6d, +/* 0x6e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0x6e8: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x6e, +/* 0x6f0: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x60, 0x60, 0x6f, +/* 0x6f8: */ 0x00, 0x60, 0x60, 0x6f, 0x60, 0x6f, 0x6f, 0x6f, +/* 0x700: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x708: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x710: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x718: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0x720: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x728: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0x730: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0x738: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x60, 0x73, +/* 0x740: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x748: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0x750: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0x758: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x60, 0x60, 0x75, +/* 0x760: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, +/* 0x768: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x76, +/* 0x770: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x77, +/* 0x778: */ 0x00, 0x70, 0x70, 0x77, 0x70, 0x77, 0x77, 0x77, +/* 0x780: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x788: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, +/* 0x790: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, +/* 0x798: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x79, +/* 0x7a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, +/* 0x7a8: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x70, 0x70, 0x7a, +/* 0x7b0: */ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x70, 0x7b, +/* 0x7b8: */ 0x40, 0x70, 0x70, 0x7b, 0x78, 0x7b, 0x7b, 0x7b, +/* 0x7c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, +/* 0x7c8: */ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x70, 0x7c, +/* 0x7d0: */ 0x00, 0x00, 0x00, 0x70, 0x40, 0x70, 0x70, 0x7d, +/* 0x7d8: */ 0x40, 0x70, 0x78, 0x7d, 0x78, 0x7d, 0x7d, 0x7d, +/* 0x7e0: */ 0x00, 0x40, 0x40, 0x78, 0x60, 0x78, 0x78, 0x7e, +/* 0x7e8: */ 0x60, 0x78, 0x78, 0x7e, 0x7c, 0x7e, 0x7e, 0x7e, +/* 0x7f0: */ 0x70, 0x7c, 0x7c, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, +/* 0x7f8: */ 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, +/* 0x800: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x808: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x810: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x818: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x820: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x828: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x830: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x838: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x840: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x848: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x850: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x858: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x860: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x868: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x870: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x878: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x880: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x888: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x890: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x898: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, +/* 0x900: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x908: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x910: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x918: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x920: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x928: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x930: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x938: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x940: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x948: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x950: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x958: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x960: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x968: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x970: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x978: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, +/* 0x980: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x988: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x990: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x998: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, +/* 0x9c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1f, +/* 0xa00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, +/* 0xa80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xab0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xab8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +/* 0xac0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xac8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xad0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xad8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xae0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xae8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, +/* 0xb00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, +/* 0xb80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xba0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xba8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, +/* 0xbc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, +/* 0xbe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, +/* 0xbf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x3f, +/* 0xbf8: */ 0x00, 0x30, 0x38, 0x3f, 0x3e, 0x3f, 0x3f, 0x3f, +/* 0xc00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, +/* 0xc80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xca0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xca8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xce0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xce8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, +/* 0xd00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, +/* 0xd80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xda0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xda8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, +/* 0xdc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, +/* 0xde0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xde8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, +/* 0xdf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x5f, +/* 0xdf8: */ 0x00, 0x40, 0x40, 0x5f, 0x5c, 0x5f, 0x5f, 0x5f, +/* 0xe00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, +/* 0xe80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xea0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xea8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xeb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xeb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6b, +/* 0xec0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xec8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xed0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xed8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6d, +/* 0xee0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0xee8: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x6e, +/* 0xef0: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x60, 0x60, 0x6f, +/* 0xef8: */ 0x00, 0x60, 0x60, 0x6f, 0x60, 0x6f, 0x6f, 0x6f, +/* 0xf00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0xf20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0xf30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0xf38: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x60, 0x73, +/* 0xf40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0xf50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, +/* 0xf58: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x60, 0x60, 0x75, +/* 0xf60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, +/* 0xf68: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x76, +/* 0xf70: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x77, +/* 0xf78: */ 0x00, 0x70, 0x70, 0x77, 0x70, 0x77, 0x77, 0x77, +/* 0xf80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, +/* 0xf90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, +/* 0xf98: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x79, +/* 0xfa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, +/* 0xfa8: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x70, 0x70, 0x7a, +/* 0xfb0: */ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x70, 0x7b, +/* 0xfb8: */ 0x40, 0x70, 0x70, 0x7b, 0x78, 0x7b, 0x7b, 0x7b, +/* 0xfc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, +/* 0xfc8: */ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x70, 0x7c, +/* 0xfd0: */ 0x00, 0x00, 0x00, 0x70, 0x40, 0x70, 0x70, 0x7d, +/* 0xfd8: */ 0x40, 0x70, 0x78, 0x7d, 0x78, 0x7d, 0x7d, 0x7d, +/* 0xfe0: */ 0x00, 0x40, 0x40, 0x78, 0x60, 0x78, 0x78, 0x7e, +/* 0xfe8: */ 0x60, 0x78, 0x78, 0x7e, 0x7c, 0x7e, 0x7e, 0x7e, +/* 0xff0: */ 0x70, 0x7c, 0x7c, 0x7f, 0x7c, 0x7f, 0x7f, 0x7f, +/* 0xff8: */ 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, +}; + +const reg8 WaveformGenerator::wave6581_PST[] = { +/* 0x000: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x008: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x010: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x018: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x020: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x028: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x030: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x038: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x040: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x048: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x050: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x058: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x060: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x068: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x070: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x078: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x080: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x088: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x090: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x098: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x0f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x100: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x108: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x110: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x118: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x120: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x128: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x130: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x138: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x140: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x148: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x150: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x158: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x160: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x168: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x170: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x178: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x180: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x188: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x190: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x198: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x1f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x200: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x208: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x210: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x218: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x220: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x228: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x230: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x238: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x240: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x248: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x250: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x258: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x260: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x268: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x270: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x278: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x280: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x288: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x290: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x298: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x2f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x300: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x308: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x310: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x318: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x320: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x328: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x330: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x338: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x340: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x348: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x350: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x358: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x360: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x368: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x370: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x378: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x380: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x388: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x390: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x398: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x3f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, +/* 0x400: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x408: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x410: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x418: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x420: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x428: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x430: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x438: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x440: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x448: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x450: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x458: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x460: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x468: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x470: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x478: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x480: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x488: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x490: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x498: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x4f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x500: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x508: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x510: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x518: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x520: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x528: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x530: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x538: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x540: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x548: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x550: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x558: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x560: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x568: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x570: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x578: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x580: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x588: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x590: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x598: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x5f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x600: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x608: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x610: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x618: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x620: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x628: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x630: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x638: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x640: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x648: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x650: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x658: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x660: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x668: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x670: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x678: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x680: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x688: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x690: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x698: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x6f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x700: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x708: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x710: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x718: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x720: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x728: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x730: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x738: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x740: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x748: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x750: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x758: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x760: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x768: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x770: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x778: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x780: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x788: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x790: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x798: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x7e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, +/* 0x7f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, +/* 0x7f8: */ 0x00, 0x00, 0x00, 0x78, 0x78, 0x7e, 0x7f, 0x7f, +/* 0x800: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x808: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x810: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x818: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x820: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x828: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x830: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x838: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x840: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x848: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x850: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x858: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x860: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x868: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x870: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x878: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x880: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x888: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x890: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x898: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x8f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x900: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x908: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x910: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x918: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x920: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x928: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x930: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x938: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x940: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x948: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x950: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x958: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x960: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x968: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x970: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x978: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x980: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x988: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x990: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x998: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0x9f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xa98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xab0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xab8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xac0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xac8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xad0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xad8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xae0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xae8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xaf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xb98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xba0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xba8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xbf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, +/* 0xc00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xc98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xca0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xca8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xce0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xce8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xcf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xd98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xda0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xda8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xde0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xde8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xdf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xe98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xea0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xea8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xeb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xeb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xec0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xec8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xed0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xed8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xee0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xee8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xef0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xef8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xf98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 0xfe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, +/* 0xff0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, +/* 0xff8: */ 0x00, 0x00, 0x00, 0x78, 0x78, 0x7e, 0x7f, 0x7f, +}; + +} +#endif diff --git a/audio/softsynth/ym2612.cpp b/audio/softsynth/ym2612.cpp new file mode 100644 index 0000000000..94fcfb97db --- /dev/null +++ b/audio/softsynth/ym2612.cpp @@ -0,0 +1,789 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include + +#include "audio/softsynth/ym2612.h" +#include "common/util.h" +#include "audio/musicplugin.h" +#include "common/translation.h" + +//////////////////////////////////////// +// +// Miscellaneous +// +//////////////////////////////////////// + +static int *sintbl = 0; +static int *powtbl = 0; +static int *frequencyTable = 0; +static int *keycodeTable = 0; +static int *keyscaleTable = 0; +static int *attackOut = 0; + + +//////////////////////////////////////// +// +// Operator2612 implementation +// +//////////////////////////////////////// + +Operator2612::Operator2612 (Voice2612 *owner) : + _owner (owner), + _state (_s_ready), + _currentLevel ((int32)0x7f << 15), + _phase (0), + _lastOutput (0), + _feedbackLevel (0), + _detune (0), + _multiple (1), + _keyScale (0), + _specifiedTotalLevel (127), + _specifiedAttackRate (0), + _specifiedDecayRate (0), + _specifiedSustainRate (0), + _specifiedReleaseRate (15) { + velocity(0); +} + +Operator2612::~Operator2612() +{ } + +void Operator2612::velocity(int velo) { + _velocity = velo; + _totalLevel = ((int32)_specifiedTotalLevel << 15) + + ((int32)(127-_velocity) << 13); + _sustainLevel = ((int32)_specifiedSustainLevel << 17); +} + +void Operator2612::feedbackLevel(int level) { + _feedbackLevel = level; +} + +void Operator2612::setInstrument(byte const *instrument) { + _detune = (instrument[8] >> 4) & 7; + _multiple = instrument[8] & 15; + _specifiedTotalLevel = instrument[12] & 127; + _keyScale = (instrument[16] >> 6) & 3; + _specifiedAttackRate = instrument[16] & 31; + _specifiedDecayRate = instrument[20] & 31; + _specifiedSustainRate = instrument[24] & 31; + _specifiedSustainLevel = (instrument[28] >> 4) & 15; + _specifiedReleaseRate = instrument[28] & 15; + _state = _s_ready; + velocity(_velocity); +} + +void Operator2612::keyOn() { + _state = _s_attacking; + _tickCount = 0; + _phase = 0; + _currentLevel = ((int32)0x7f << 15); +} + +void Operator2612::keyOff() { + if (_state != _s_ready) + _state = _s_releasing; +} + +void Operator2612::frequency(int freq) { + double value; // Use for intermediate computations to avoid int64 arithmetic + int r; + + _frequency = freq / _owner->_rate; + + r = _specifiedAttackRate; + if (r != 0) { + r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale)); + if (r >= 64) + r = 63; + } + + r = 63 - r; + if (_specifiedTotalLevel >= 128) + value = 0; + else { + value = powtbl[(r&3) << 7]; + value *= 1 << (r >> 2); + value *= 41; + value /= 1 << (15 + 5); + value *= 127 - _specifiedTotalLevel; + value /= 127; + } + _attackTime = (int32) value; // 1 ?? == (1 << 12) + if (_attackTime > 0) + _attackTime = (1 << (12+10)) / (_owner->_rate * _attackTime); + + r = _specifiedDecayRate; + if (r != 0) { + r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale)); + if (r >= 64) + r = 63; + } + value = (double) powtbl[(r&3) << 7] * (0x10 << (r>>2)) / 31; + _decayRate = (int32) value / _owner->_rate; + + r = _specifiedSustainRate; + if (r != 0) { + r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale)); + if (r >= 64) + r = 63; + } + value = (double) powtbl[(r&3) << 7] * (0x10 << (r>>2)) / 31; + _sustainRate = (int32) value / _owner->_rate; + + r = _specifiedReleaseRate; + if (r != 0) { + r = r * 2 + 1; // (Translated) I cannot know whether the timing is a good choice or not + r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale)); + // KS + if (r >= 64) + r = 63; + } + value = (double) powtbl[(r&3) << 7] * (0x10 << (r>>2)) / 31; + _releaseRate = (int32) value / _owner->_rate; +} + +void Operator2612::nextTick(const int *phasebuf, int *outbuf, int buflen) { + if (_state == _s_ready) + return; + if (_state == _s_attacking && _attackTime <= 0) { + _currentLevel = 0; + _state = _s_decaying; + } + + int32 levelIncrement = 0; + int32 target = 0; + State next_state = _s_ready; + const int32 zero_level = ((int32)0x7f << 15); + const int phaseIncrement = (_multiple > 0) ? (_frequency * _multiple) : (_frequency / 2); + + int32 output = _lastOutput; + int32 level = _currentLevel + _totalLevel; + + while (buflen) { + switch (_state) { + case _s_ready: + return; + case _s_attacking: + next_state = _s_attacking; + break; + case _s_decaying: + levelIncrement = _decayRate; + target = _sustainLevel + _totalLevel; + next_state = _s_sustaining; + break; + case _s_sustaining: + levelIncrement = _sustainRate; + target = zero_level + _totalLevel; + next_state = _s_ready; + break; + case _s_releasing: + levelIncrement = _releaseRate; + target = zero_level + _totalLevel; + next_state = _s_ready; + break; + } + + bool switching = false; + do { + if (next_state == _s_attacking) { + // Attack phase + ++_tickCount; + int i = (int) (_tickCount * _attackTime); + if (i >= 1024) { + level = _totalLevel; + _state = _s_decaying; + switching = true; + } else { + level = (attackOut[i] << (31 - 8 - 16)) + _totalLevel; + } + } else { + // Decay, Sustain and Release phases + level += levelIncrement; + if (level >= target) { + level = target; + _state = next_state; + switching = true; + } + } + + if (level < zero_level) { + int phaseShift = *phasebuf >> 2; + if (_feedbackLevel) + phaseShift += (output << (_feedbackLevel - 1)) / 1024; + output = sintbl[((_phase >> 7) + phaseShift) & 0x7ff]; + output >>= (level >> 18); + // Here is the original code, which requires 64-bit ints +// output *= powtbl[511 - ((level>>25)&511)]; +// output >>= 16; +// output >>= 1; + // And here's our 32-bit trick for doing it. (Props to Fingolfin!) + // Result varies from original code by max of 1. +// int powVal = powtbl[511 - ((level>>9)&511)]; +// int outputHI = output / 256; +// int powHI = powVal / 256; +// output = (outputHI * powHI) / 2 + (outputHI * (powVal % 256) + powHI * (output % 256)) / 512; + // And here's the even faster code. + // Result varies from original code by max of 8. + output = ((output >> 4) * (powtbl[511-((level>>9)&511)] >> 3)) / 1024; + + _phase += phaseIncrement; + _phase &= 0x3ffff; + } else + output = 0; + + *outbuf += output; + --buflen; + ++phasebuf; + ++outbuf; + } while (buflen && !switching); + } + _lastOutput = output; + _currentLevel = level - _totalLevel; +} + +//////////////////////////////////////// +// +// Voice2612 implementation +// +//////////////////////////////////////// + +Voice2612::Voice2612() { + next = 0; + _control7 = 127; + _note = 40; + _frequency = 440; + _frequencyOffs = 0x2000; + _algorithm = 7; + + _buffer = 0; + _buflen = 0; + + int i; + for (i = 0; i < ARRAYSIZE(_opr); ++i) + _opr[i] = new Operator2612 (this); + velocity(0); +} + +Voice2612::~Voice2612() { + int i; + for (i = 0; i < ARRAYSIZE(_opr); ++i) + delete _opr[i]; + free(_buffer); +} + +void Voice2612::velocity(int velo) { + _velocity = velo; +#if 0 + int v = (velo * _control7) >> 7; +#else + int v = velo + (_control7 - 127) * 4; +#endif + bool iscarrier[8][4] = { + { false, false, false, true, }, //0 + { false, false, false, true, }, //1 + { false, false, false, true, }, //2 + { false, false, false, true, }, //3 + { false, true, false, true, }, //4 + { false, true, true, true, }, //5 + { false, true, true, true, }, //6 + { true, true, true, true, }, //7 + }; + int opr; + for (opr = 0; opr < 4; opr++) + if (iscarrier[_algorithm][opr]) + _opr[opr]->velocity(v); + else + _opr[opr]->velocity(127); +} + +void Voice2612::setControlParameter(int control, int value) { + switch (control) { + case 7: + _control7 = value; + velocity(_velocity); + break; + case 123: + // All notes off + noteOff(_note); + }; +} + +void Voice2612::setInstrument(byte const *instrument) { + if (instrument == NULL) + return; + + _algorithm = instrument[32] & 7; + _opr[0]->feedbackLevel((instrument[32] >> 3) & 7); + _opr[1]->feedbackLevel(0); + _opr[2]->feedbackLevel(0); + _opr[3]->feedbackLevel(0); + _opr[0]->setInstrument(instrument + 0); + _opr[1]->setInstrument(instrument + 2); + _opr[2]->setInstrument(instrument + 1); + _opr[3]->setInstrument(instrument + 3); +} + +void Voice2612::nextTick(int *outbuf, int buflen) { + if (_velocity == 0) + return; + + if (_buflen < buflen) { + free(_buffer); + _buflen = buflen; + _buffer = (int *) malloc(sizeof(int) * buflen * 2); + } + + int *buf1 = _buffer; + int *buf2 = _buffer + buflen; + memset(_buffer, 0, sizeof(int) * buflen * 2); + + switch (_algorithm) { + case 0: + _opr[0]->nextTick(buf1, buf2, buflen); + _opr[1]->nextTick(buf2, buf1, buflen); + memset (buf2, 0, sizeof (int) * buflen); + _opr[2]->nextTick(buf1, buf2, buflen); + _opr[3]->nextTick(buf2, outbuf, buflen); + break; + case 1: + _opr[0]->nextTick(buf1, buf2, buflen); + _opr[1]->nextTick(buf1, buf2, buflen); + _opr[2]->nextTick(buf2, buf1, buflen); + _opr[3]->nextTick(buf1, outbuf, buflen); + break; + case 2: + _opr[1]->nextTick(buf1, buf2, buflen); + _opr[2]->nextTick(buf2, buf1, buflen); + memset(buf2, 0, sizeof(int) * buflen); + _opr[0]->nextTick(buf2, buf1, buflen); + _opr[3]->nextTick(buf1, outbuf, buflen); + break; + case 3: + _opr[0]->nextTick(buf1, buf2, buflen); + _opr[1]->nextTick(buf2, buf1, buflen); + memset(buf2, 0, sizeof(int) * buflen); + _opr[2]->nextTick(buf2, buf1, buflen); + _opr[3]->nextTick(buf1, outbuf, buflen); + break; + case 4: + _opr[0]->nextTick(buf1, buf2, buflen); + _opr[1]->nextTick(buf2, outbuf, buflen); + _opr[2]->nextTick(buf1, buf1, buflen); + _opr[3]->nextTick(buf1, outbuf, buflen); + break; + case 5: + _opr[0]->nextTick(buf1, buf2, buflen); + _opr[1]->nextTick(buf2, outbuf, buflen); + _opr[2]->nextTick(buf2, outbuf, buflen); + _opr[3]->nextTick(buf2, outbuf, buflen); + break; + case 6: + _opr[0]->nextTick(buf1, buf2, buflen); + _opr[1]->nextTick(buf2, outbuf, buflen); + _opr[2]->nextTick(buf1, outbuf, buflen); + _opr[3]->nextTick(buf1, outbuf, buflen); + break; + case 7: + _opr[0]->nextTick(buf1, outbuf, buflen); + _opr[1]->nextTick(buf1, outbuf, buflen); + _opr[2]->nextTick(buf1, outbuf, buflen); + _opr[3]->nextTick(buf1, outbuf, buflen); + break; + }; +} + +void Voice2612::noteOn(int n, int onVelo) { + _note = n; + velocity(onVelo); + recalculateFrequency(); + int i; + for (i = 0; i < ARRAYSIZE(_opr); i++) + _opr[i]->keyOn(); +} + +bool Voice2612::noteOff(int note) { + if (_note != note) + return false; + int i; + for (i = 0; i < ARRAYSIZE(_opr); i++) + _opr[i]->keyOff(); + return true; +} + +void Voice2612::pitchBend(int value) { + _frequencyOffs = value; + recalculateFrequency(); +} + +void Voice2612::recalculateFrequency() { + // + // + // + int32 basefreq = frequencyTable[_note]; + int cfreq = frequencyTable[_note - (_note % 12)]; + int oct = _note / 12; + int fnum = (int) (((double)basefreq * (1 << 13)) / cfreq); + fnum += _frequencyOffs - 0x2000; + if (fnum < 0x2000) { + fnum += 0x2000; + oct--; + } + if (fnum >= 0x4000) { + fnum -= 0x2000; + oct++; + } + + // + _frequency = (int) ((frequencyTable[oct*12] * (double)fnum) / 8); + + int i; + for (i = 0; i < ARRAYSIZE(_opr); i++) + _opr[i]->frequency(_frequency); +} + +//////////////////////////////////////// +// +// MidiChannel_YM2612 +// +//////////////////////////////////////// + +MidiChannel_YM2612::MidiChannel_YM2612() { + _voices = 0; + _next_voice = 0; +} + +MidiChannel_YM2612::~MidiChannel_YM2612() { + removeAllVoices(); +} + +void MidiChannel_YM2612::removeAllVoices() { + if (!_voices) + return; + Voice2612 *last, *voice = _voices; + for (; voice; voice = last) { + last = voice->next; + delete voice; + } + _voices = _next_voice = 0; +} + +void MidiChannel_YM2612::noteOn(byte note, byte onVelo) { + if (!_voices) + return; + _next_voice = _next_voice ? _next_voice : _voices; + _next_voice->noteOn(note, onVelo); + _next_voice = _next_voice->next; +} + +void MidiChannel_YM2612::noteOff(byte note) { + if (!_voices) + return; + if (_next_voice == _voices) + _next_voice = 0; + Voice2612 *voice = _next_voice; + do { + if (!voice) + voice = _voices; + if (voice->noteOff(note)) { + _next_voice = voice; + break; + } + voice = voice->next; + } while (voice != _next_voice); +} + +void MidiChannel_YM2612::controlChange(byte control, byte value) { + // + if (control == 121) { + // Reset controller + removeAllVoices(); + } else { + Voice2612 *voice = _voices; + for (; voice; voice = voice->next) + voice->setControlParameter(control, value); + } +} + +void MidiChannel_YM2612::sysEx_customInstrument(uint32 type, const byte *fmInst) { + if (type != 'EUP ') + return; + Voice2612 *voice = new Voice2612; + voice->next = _voices; + _voices = voice; + voice->_rate = _rate; + voice->setInstrument(fmInst); +} + +void MidiChannel_YM2612::pitchBend(int16 value) { + // + Voice2612 *voice = _voices; + for (; voice; voice = voice->next) + voice->pitchBend(value); +} + +void MidiChannel_YM2612::nextTick(int *outbuf, int buflen) { + Voice2612 *voice = _voices; + for (; voice; voice = voice->next) + voice->nextTick(outbuf, buflen); +} + +void MidiChannel_YM2612::rate(uint16 r) { + _rate = r; + Voice2612 *voice = _voices; + for (; voice; voice = voice->next) + voice->_rate = r; +} + +//////////////////////////////////////// +// +// MidiDriver_YM2612 +// +//////////////////////////////////////// + +MidiDriver_YM2612::MidiDriver_YM2612(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer) { + _next_voice = 0; + + createLookupTables(); + _volume = 256; + int i; + for (i = 0; i < ARRAYSIZE(_channel); i++) + _channel[i] = new MidiChannel_YM2612; + rate(getRate()); +} + +MidiDriver_YM2612::~MidiDriver_YM2612() { + int i; + for (i = 0; i < ARRAYSIZE(_channel); i++) + delete _channel[i]; + removeLookupTables(); +} + +int MidiDriver_YM2612::open() { + if (_isOpen) + return MERR_ALREADY_OPEN; + + MidiDriver_Emulated::open(); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + return 0; +} + +void MidiDriver_YM2612::close() { + if (!_isOpen) + return; + _isOpen = false; + + _mixer->stopHandle(_mixerSoundHandle); +} + +void MidiDriver_YM2612::send(uint32 b) { + send(b & 0xF, b & 0xFFFFFFF0); +} + +void MidiDriver_YM2612::send(byte chan, uint32 b) { + //byte param3 = (byte) ((b >> 24) & 0xFF); + byte param2 = (byte) ((b >> 16) & 0xFF); + byte param1 = (byte) ((b >> 8) & 0xFF); + byte cmd = (byte) (b & 0xF0); + if (chan > ARRAYSIZE(_channel)) + return; + + switch (cmd) { + case 0x80:// Note Off + _channel[chan]->noteOff(param1); + break; + case 0x90: // Note On + _channel[chan]->noteOn(param1, param2); + break; + case 0xA0: // Aftertouch + break; // Not supported. + case 0xB0: // Control Change + _channel[chan]->controlChange(param1, param2); + break; + case 0xC0: // Program Change + _channel[chan]->programChange(param1); + break; + case 0xD0: // Channel Pressure + break; // Not supported. + case 0xE0: // Pitch Bend + _channel[chan]->pitchBend((param1 | (param2 << 7)) - 0x2000); + break; + case 0xF0: // SysEx + // We should never get here! SysEx information has to be + // sent via high-level semantic methods. + warning("MidiDriver_YM2612: Receiving SysEx command on a send() call"); + break; + + default: + warning("MidiDriver_YM2612: Unknown send() command 0x%02X", cmd); + } +} + +void MidiDriver_YM2612::sysEx(const byte *msg, uint16 length) { + if (msg[0] != 0x7C || msg[1] >= ARRAYSIZE(_channel)) + return; + _channel[msg[1]]->sysEx_customInstrument('EUP ', &msg[2]); +} + +void MidiDriver_YM2612::generateSamples(int16 *data, int len) { + memset(data, 0, 2 * sizeof(int16) * len); + nextTick(data, len); +} + +void MidiDriver_YM2612::nextTick(int16 *buf1, int buflen) { + int *buf0 = (int *)buf1; + + int i; + for (i = 0; i < ARRAYSIZE(_channel); i++) + _channel[i]->nextTick(buf0, buflen); + + for (i = 0; i < buflen; ++i) + buf1[i*2+1] = buf1[i*2] = ((buf0[i] * volume()) >> 10) & 0xffff; +} + +void MidiDriver_YM2612::rate(uint16 r) +{ + int i; + for (i = 0; i < ARRAYSIZE(_channel); i++) + _channel[i]->rate(r); +} + +void MidiDriver_YM2612::createLookupTables() { + { + int i; + sintbl = new int [2048]; + for (i = 0; i < 2048; i++) + sintbl[i] = (int)(0xffff * sin(i/2048.0*2.0*PI)); + } + + { + int i; + powtbl = new int [1025]; + for (i = 0; i <= 1024; i++) + powtbl[i] = (int)(0x10000 * pow(2.0, (i-512)/512.0)); + } + + { + int i; + int block; + + static int fnum[] = { + 0x026a, 0x028f, 0x02b6, 0x02df, + 0x030b, 0x0339, 0x036a, 0x039e, + 0x03d5, 0x0410, 0x044e, 0x048f, + }; + + // (int)(880.0 * 256.0 * pow(2.0, (note-0x51)/12.0)) + // + frequencyTable = new int [120]; + for (block = -1; block < 9; block++) { + for (i = 0; i < 12; i++) { + double freq = fnum[i] * (166400.0 / 3) * pow(2.0, block-21); + frequencyTable[(block+1)*12+i] = (int)(256.0 * freq); + } + } + + keycodeTable = new int [120]; + // detune + for (block = -1; block < 9; block++) { + for (i = 0; i < 12; i++) { + // see p.204 + int f8 = (fnum[i] >> 7) & 1; + int f9 = (fnum[i] >> 8) & 1; + int f10 = (fnum[i] >> 9) & 1; + int f11 = (fnum[i] >> 10) & 1; + int n4 = f11; + int n3 = (f11&(f10|f9|f8)) | (~f11&f10&f9&f8); + int note = n4*2 + n3; + // see p.207 + keycodeTable[(block+1)*12+i] = block*4 + note; + } + } + } + + { + int freq; + keyscaleTable = new int [8192]; + keyscaleTable[0] = 0; + for (freq = 1; freq < 8192; freq++) { + keyscaleTable[freq] = (int)(log((double)freq) / 9.03 * 32.0) - 1; + // 8368[Hz] (o9c) + } + } + + { + int i; + attackOut = new int [1024]; + for (i = 0; i < 1024; i++) + attackOut[i] = (int)(((0x7fff+0x03a5)*30.0) / (30.0+i)) - 0x03a5; + } +} + +void MidiDriver_YM2612::removeLookupTables() { + delete[] sintbl; + delete[] powtbl; + delete[] frequencyTable; + delete[] keycodeTable; + delete[] keyscaleTable; + delete[] attackOut; + sintbl = powtbl = frequencyTable = keycodeTable = keyscaleTable = attackOut = 0; +} + + +// Plugin interface + +class TownsEmuMusicPlugin : public MusicPluginObject { +public: + const char *getName() const { + return _s("FM Towns Emulator"); + } + + const char *getId() const { + return "towns"; + } + + MusicDevices getDevices() const; + Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const; +}; + +MusicDevices TownsEmuMusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_TOWNS)); + return devices; +} + +Common::Error TownsEmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const { + *mididriver = new MidiDriver_YM2612(g_system->getMixer()); + + return Common::kNoError; +} + +//#if PLUGIN_ENABLED_DYNAMIC(TOWNS) + //REGISTER_PLUGIN_DYNAMIC(TOWNS, PLUGIN_TYPE_MUSIC, TownsEmuMusicPlugin); +//#else + REGISTER_PLUGIN_STATIC(TOWNS, PLUGIN_TYPE_MUSIC, TownsEmuMusicPlugin); +//#endif diff --git a/audio/softsynth/ym2612.h b/audio/softsynth/ym2612.h new file mode 100644 index 0000000000..f652b2d9e4 --- /dev/null +++ b/audio/softsynth/ym2612.h @@ -0,0 +1,179 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef SOUND_SOFTSYNTH_Y2612_H +#define SOUND_SOFTSYNTH_Y2612_H + +#include "common/scummsys.h" + +#include "audio/softsynth/emumidi.h" + +//////////////////////////////////////// +// +// Class declarations +// +//////////////////////////////////////// + +class Voice2612; +class Operator2612 { +protected: + Voice2612 *_owner; + enum State { _s_ready, _s_attacking, _s_decaying, _s_sustaining, _s_releasing }; + State _state; + int32 _currentLevel; + int _frequency; + uint32 _phase; + int _lastOutput; + int _feedbackLevel; + int _detune; + int _multiple; + int32 _totalLevel; + int _keyScale; + int _velocity; + int _specifiedTotalLevel; + int _specifiedAttackRate; + int _specifiedDecayRate; + int _specifiedSustainLevel; + int _specifiedSustainRate; + int _specifiedReleaseRate; + int _tickCount; + int _attackTime; + int32 _decayRate; + int32 _sustainLevel; + int32 _sustainRate; + int32 _releaseRate; + +public: + Operator2612 (Voice2612 *owner); + ~Operator2612(); + void feedbackLevel(int level); + void setInstrument(byte const *instrument); + void velocity(int velo); + void keyOn(); + void keyOff(); + void frequency(int freq); + void nextTick(const int *phaseShift, int *outbuf, int buflen); + bool inUse() { return (_state != _s_ready); } +}; + +class Voice2612 { +public: + Voice2612 *next; + uint16 _rate; + +protected: + Operator2612 *_opr[4]; + int _velocity; + int _control7; + int _note; + int _frequencyOffs; + int _frequency; + int _algorithm; + + int *_buffer; + int _buflen; + +public: + Voice2612(); + ~Voice2612(); + void setControlParameter(int control, int value); + void setInstrument(byte const *instrument); + void velocity(int velo); + void nextTick(int *outbuf, int buflen); + void noteOn(int n, int onVelo); + bool noteOff(int note); + void pitchBend(int value); + void recalculateFrequency(); +}; + +class MidiChannel_YM2612 : public MidiChannel { +protected: + uint16 _rate; + Voice2612 *_voices; + Voice2612 *_next_voice; + +public: + void removeAllVoices(); + void nextTick(int *outbuf, int buflen); + void rate(uint16 r); + +public: + MidiChannel_YM2612(); + virtual ~MidiChannel_YM2612(); + + // MidiChannel interface + MidiDriver *device() { return 0; } + byte getNumber() { return 0; } + void release() { } + void send(uint32 b) { } + void noteOff(byte note); + void noteOn(byte note, byte onVelo); + void programChange(byte program) { } + void pitchBend(int16 value); + void controlChange(byte control, byte value); + void pitchBendFactor(byte value) { } + void sysEx_customInstrument(uint32 type, const byte *instr); +}; + +class MidiDriver_YM2612 : public MidiDriver_Emulated { +protected: + MidiChannel_YM2612 *_channel[16]; + + int _next_voice; + int _volume; + +protected: + void nextTick(int16 *buf1, int buflen); + int volume(int val = -1) { if (val >= 0) _volume = val; return _volume; } + void rate(uint16 r); + + void generateSamples(int16 *buf, int len); + +public: + MidiDriver_YM2612(Audio::Mixer *mixer); + virtual ~MidiDriver_YM2612(); + + static void createLookupTables(); + static void removeLookupTables(); + + int open(); + void close(); + void send(uint32 b); + void send(byte channel, uint32 b); // Supports higher than channel 15 + uint32 property(int prop, uint32 param) { return 0; } + + void setPitchBendRange(byte channel, uint range) { } + void sysEx(const byte *msg, uint16 length); + + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + + + // AudioStream API + bool isStereo() const { return true; } + int getRate() const { return _mixer->getOutputRate(); } +}; + +#endif + diff --git a/audio/timestamp.cpp b/audio/timestamp.cpp new file mode 100644 index 0000000000..cdc5bc3a02 --- /dev/null +++ b/audio/timestamp.cpp @@ -0,0 +1,212 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/timestamp.h" +#include "common/algorithm.h" + +namespace Audio { + +Timestamp::Timestamp(uint ms, uint fr) { + assert(fr > 0); + + _secs = ms / 1000; + _framerateFactor = 1000 / Common::gcd(1000, fr); + _framerate = fr * _framerateFactor; + + // Note that _framerate is always divisible by 1000. + _numFrames = (ms % 1000) * (_framerate / 1000); +} + +Timestamp::Timestamp(uint s, uint frames, uint fr) { + assert(fr > 0); + + _secs = s; + _framerateFactor = 1000 / Common::gcd(1000, fr); + _framerate = fr * _framerateFactor; + _numFrames = frames * _framerateFactor; + + normalize(); +} + +Timestamp Timestamp::convertToFramerate(uint newFramerate) const { + Timestamp ts(*this); + + if (ts.framerate() != newFramerate) { + ts._framerateFactor = 1000 / Common::gcd(1000, newFramerate); + ts._framerate = newFramerate * ts._framerateFactor; + + const uint g = Common::gcd(_framerate, ts._framerate); + const uint p = _framerate / g; + const uint q = ts._framerate / g; + + // Convert the frame offset to the new framerate. + // We round to the nearest (as opposed to always + // rounding down), to minimize rounding errors during + // round trip conversions. + ts._numFrames = (ts._numFrames * q + p/2) / p; + + ts.normalize(); + } + + return ts; +} + +void Timestamp::normalize() { + // Convert negative _numFrames values to positive ones by adjusting _secs + if (_numFrames < 0) { + int secsub = 1 + (-_numFrames / _framerate); + + _numFrames += _framerate * secsub; + _secs -= secsub; + } + + // Wrap around if necessary + _secs += (_numFrames / _framerate); + _numFrames %= _framerate; +} + +bool Timestamp::operator==(const Timestamp &ts) const { + return cmp(ts) == 0; +} + +bool Timestamp::operator!=(const Timestamp &ts) const { + return cmp(ts) != 0; +} + +bool Timestamp::operator<(const Timestamp &ts) const { + return cmp(ts) < 0; +} + +bool Timestamp::operator<=(const Timestamp &ts) const { + return cmp(ts) <= 0; +} + +bool Timestamp::operator>(const Timestamp &ts) const { + return cmp(ts) > 0; +} + +bool Timestamp::operator>=(const Timestamp &ts) const { + return cmp(ts) >= 0; +} + +int Timestamp::cmp(const Timestamp &ts) const { + int delta = _secs - ts._secs; + if (!delta) { + const uint g = Common::gcd(_framerate, ts._framerate); + const uint p = _framerate / g; + const uint q = ts._framerate / g; + + delta = (_numFrames * q - ts._numFrames * p); + } + + return delta; +} + + +Timestamp Timestamp::addFrames(int frames) const { + Timestamp ts(*this); + + // The frames are given in the original framerate, so we have to + // adjust by _framerateFactor accordingly. + ts._numFrames += frames * _framerateFactor; + ts.normalize(); + + return ts; +} + +Timestamp Timestamp::addMsecs(int ms) const { + Timestamp ts(*this); + ts._secs += ms / 1000; + // Add the remaining frames. Note that _framerate is always divisible by 1000. + ts._numFrames += (ms % 1000) * (ts._framerate / 1000); + + ts.normalize(); + + return ts; +} + +void Timestamp::addIntern(const Timestamp &ts) { + assert(_framerate == ts._framerate); + _secs += ts._secs; + _numFrames += ts._numFrames; + + normalize(); +} + +Timestamp Timestamp::operator-() const { + Timestamp result(*this); + result._secs = -_secs; + result._numFrames = -_numFrames; + result.normalize(); + return result; +} + +Timestamp Timestamp::operator+(const Timestamp &ts) const { + Timestamp result(*this); + result.addIntern(ts); + return result; +} + +Timestamp Timestamp::operator-(const Timestamp &ts) const { + Timestamp result(*this); + result.addIntern(-ts); + return result; +} + +int Timestamp::frameDiff(const Timestamp &ts) const { + + int delta = 0; + if (_secs != ts._secs) + delta = (_secs - ts._secs) * _framerate; + + delta += _numFrames; + + if (_framerate == ts._framerate) { + delta -= ts._numFrames; + } else { + // We need to multiply by the quotient of the two framerates. + // We cancel the GCD in this fraction to reduce the risk of + // overflows. + const uint g = Common::gcd(_framerate, ts._framerate); + const uint p = _framerate / g; + const uint q = ts._framerate / g; + + delta -= ((long)ts._numFrames * p + q/2) / (long)q; + } + + return delta / (int)_framerateFactor; +} + +int Timestamp::msecsDiff(const Timestamp &ts) const { + return msecs() - ts.msecs(); +} + +int Timestamp::msecs() const { + // Note that _framerate is always divisible by 1000. + return _secs * 1000 + _numFrames / (_framerate / 1000); +} + + +} // End of namespace Audio diff --git a/audio/timestamp.h b/audio/timestamp.h new file mode 100644 index 0000000000..4130793fc8 --- /dev/null +++ b/audio/timestamp.h @@ -0,0 +1,251 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SOUND_TIMESTAMP_H +#define SOUND_TIMESTAMP_H + +#include "common/scummsys.h" + +namespace Audio { + +/** + * Timestamps allow specifying points in time and measuring time intervals + * with a sub-millisecond granularity. + * + * When dealing with audio and video decoding, it is often necessary to + * measure time (intervals) in terms of frames, relative to a fixed + * frame rate (that is, a fixed number of frames per seconds). For + * example, in a typical video there are 24 frames per second, and in a + * typical sound there are 44100 frames (i.e. samples for mono sound + * and pairs of samples for stereo) per second. + * + * At the same time, the system clock provided by ScummVM measures time + * in milliseconds. For syncing purposes and other reasons, it is often + * necessary to convert between and compare time measures given on the + * one hand as a frame count, and on the other hand as a number of + * milliseconds. + * + * If handled carelessly, this can introduce rounding errors that + * quickly accumulate, resulting in user noticeable disturbance, such as + * audio and video running out of sync. E.g. a typical approach is to + * measure all time in milliseconds. But with a frame rate of 24 frames + * per second, one frame is 41.66666... milliseconds long. On the other + * hand, if measuring in frames, then similar rounding issue occur when + * converting from milliseconds to frames. + * + * One solution is to use floating point arithmetic to compute with + * fractional frames resp. (milli)seconds. This has other undesirable + * side effects; foremost, some platforms ScummVM runs on still have + * only limited (and slow) floating point support. + * + * This class provides an alternate solution: It stores time in terms of + * frames, but with a twist: Client code can specify arbitrary + * (integral) framerates; but internally, Timestamp modifies the + * framerate to be a multiple of 1000. This way, both numbers of frames + * (relative to the original framerate) as well as milliseconds can be + * represented as integers. This change is completely hidden from the + * user, however. + * + * A Timestamp can be converted to a frame count or milliseconds at + * virtually no cost. Likewise, it is posible to compute the difference + * between two Timestamps in milliseconds or number of frames. + * Timestamps can be easily compared using regular comparison operators, + * resulting in nicely readable code; this is even possible for + * timestamps that are specified using different framerates. + * Client code can modify Timestamps by adding a number of frames + * to it, or adding a number of milliseconds. Adding negative amounts is + * also allowed, and a Timestamp can even represent a "negative time" + * (mainly useful when using the Timestamp to store a time interval). + */ +class Timestamp { +public: + /** + * Set up a timestamp with a given time and framerate. + * @param msecs starting time in milliseconds + * @param framerate number of frames per second (must be > 0) + */ + Timestamp(uint msecs = 0, uint framerate = 1); + + /** + * Set up a timestamp with a given time, frames and framerate. + * @param secs starting time in seconds + * @param frames starting frames + * @param framerate number of frames per second (must be > 0) + */ + Timestamp(uint secs, uint frames, uint framerate); + + /** + * Return a timestamp which represents as closely as possible + * the point in time describes by this timestamp, but with + * a different framerate. + */ + Timestamp convertToFramerate(uint newFramerate) const; + + /** + * Check whether to timestamps describe the exact same moment + * in time. This means that two timestamps can compare + * as equal even if they use different framerates. + */ + bool operator==(const Timestamp &ts) const; + bool operator!=(const Timestamp &ts) const; + bool operator<(const Timestamp &ts) const; + bool operator<=(const Timestamp &ts) const; + bool operator>(const Timestamp &ts) const; + bool operator>=(const Timestamp &ts) const; + + /** + * Returns a new timestamp, which corresponds to the time encoded + * by this timestamp with the given number of frames added. + * @param frames number of frames to add + */ + Timestamp addFrames(int frames) const; + + /** + * Returns a new timestamp, which corresponds to the time encoded + * by this timestamp with the given number of milliseconds added. + * @param msecs number of milliseconds to add + */ + Timestamp addMsecs(int msecs) const; + + + // unary minus + Timestamp operator-() const; + + /** + * Compute the sum of two timestamps. This is only + * allowed if they use the same framerate. + */ + Timestamp operator+(const Timestamp &ts) const; + + /** + * Compute the difference between two timestamps. This is only + * allowed if they use the same framerate. + */ + Timestamp operator-(const Timestamp &ts) const; + + /** + * Computes the number of frames between this timestamp and ts. + * The frames are with respect to the framerate used by this + * Timestamp (which may differ from the framerate used by ts). + */ + int frameDiff(const Timestamp &ts) const; + + /** Computes the number off milliseconds between this timestamp and ts. */ + int msecsDiff(const Timestamp &ts) const; + + /** + * Return the time in milliseconds described by this timestamp, + * rounded down. + */ + int msecs() const; + + /** + * Return the time in seconds described by this timestamp, + * rounded down. + */ + inline int secs() const { + return _secs; + } + + /** + * Return the time in frames described by this timestamp. + */ + inline int totalNumberOfFrames() const { + return _numFrames / (int)_framerateFactor + _secs * (int)(_framerate / _framerateFactor); + } + + /** + * A timestamp consists of a number of seconds, plus a number + * of frames, the latter describing a fraction of a second. + * This method returns the latter number. + */ + inline int numberOfFrames() const { + return _numFrames / (int)_framerateFactor; + } + + /** Return the framerate used by this timestamp. */ + inline uint framerate() const { return _framerate / _framerateFactor; } + +protected: + /** + * Compare this timestamp to another one and return + * a value similar to strcmp. + */ + int cmp(const Timestamp &ts) const; + + /** + * Normalize this timestamp by making _numFrames non-negative + * and reducing it modulo _framerate. + */ + void normalize(); + + /** + * Add another timestamp to this one and normalize the result. + */ + void addIntern(const Timestamp &ts); + +protected: + /** + * The seconds part of this timestamp. + * The total time in seconds represented by this timestamp can be + * computed as follows: + * _secs + (double)_numFrames / _framerate + */ + int _secs; + + /** + * The number of frames which together with _secs encodes the + * timestamp. The total number of *internal* frames represented + * by this timestamp can be computed as follows: + * _numFrames + _secs * _framerate + * To obtain the number of frames with respect to the original + * framerate, this value has to be divided by _framerateFactor. + * + * This is always a value greater or equal to zero. + * The only reason this is an int and not an uint is to + * allow intermediate negative values. + */ + int _numFrames; + + /** + * The internal framerate, i.e. the number of frames per second. + * This is computed as the least common multiple of the framerate + * specified by the client code, and 1000. + * This way, we ensure that we can store both frames and + * milliseconds without any rounding losses. + */ + uint _framerate; + + /** + * Factor by which the original framerate specified by the client + * code was multipled to obtain the internal _framerate value. + */ + uint _framerateFactor; +}; + + +} // End of namespace Audio + +#endif diff --git a/backends/audiocd/default/default-audiocd.cpp b/backends/audiocd/default/default-audiocd.cpp index 6d16b26652..0acc64b7f2 100644 --- a/backends/audiocd/default/default-audiocd.cpp +++ b/backends/audiocd/default/default-audiocd.cpp @@ -24,7 +24,7 @@ */ #include "backends/audiocd/default/default-audiocd.h" -#include "sound/audiostream.h" +#include "audio/audiostream.h" #include "common/system.h" DefaultAudioCDManager::DefaultAudioCDManager() { diff --git a/backends/audiocd/default/default-audiocd.h b/backends/audiocd/default/default-audiocd.h index 3f9df0bbbb..d3e0150fb6 100644 --- a/backends/audiocd/default/default-audiocd.h +++ b/backends/audiocd/default/default-audiocd.h @@ -27,7 +27,7 @@ #define BACKENDS_AUDIOCD_DEFAULT_H #include "backends/audiocd/audiocd.h" -#include "sound/mixer.h" +#include "audio/mixer.h" /** * The default audio cd manager. Implements emulation of audio cd playback. diff --git a/backends/midi/alsa.cpp b/backends/midi/alsa.cpp index fd32777a1b..953b088958 100644 --- a/backends/midi/alsa.cpp +++ b/backends/midi/alsa.cpp @@ -31,8 +31,8 @@ #include "common/config-manager.h" #include "common/util.h" -#include "sound/musicplugin.h" -#include "sound/mpu401.h" +#include "audio/musicplugin.h" +#include "audio/mpu401.h" #include diff --git a/backends/midi/camd.cpp b/backends/midi/camd.cpp index 7bf702de58..57251b45e1 100644 --- a/backends/midi/camd.cpp +++ b/backends/midi/camd.cpp @@ -31,8 +31,8 @@ #include "common/endian.h" #include "common/util.h" -#include "sound/musicplugin.h" -#include "sound/mpu401.h" +#include "audio/musicplugin.h" +#include "audio/mpu401.h" #include #include diff --git a/backends/midi/coreaudio.cpp b/backends/midi/coreaudio.cpp index 62e9a6d0f4..75c512cad7 100644 --- a/backends/midi/coreaudio.cpp +++ b/backends/midi/coreaudio.cpp @@ -42,8 +42,8 @@ #include "common/config-manager.h" #include "common/util.h" -#include "sound/musicplugin.h" -#include "sound/mpu401.h" +#include "audio/musicplugin.h" +#include "audio/mpu401.h" #include #include diff --git a/backends/midi/coremidi.cpp b/backends/midi/coremidi.cpp index 2df9f78383..c0663352cb 100644 --- a/backends/midi/coremidi.cpp +++ b/backends/midi/coremidi.cpp @@ -31,8 +31,8 @@ #include "common/config-manager.h" #include "common/util.h" -#include "sound/musicplugin.h" -#include "sound/mpu401.h" +#include "audio/musicplugin.h" +#include "audio/mpu401.h" #include diff --git a/backends/midi/dmedia.cpp b/backends/midi/dmedia.cpp index ce4ea571e0..f0ab84c934 100644 --- a/backends/midi/dmedia.cpp +++ b/backends/midi/dmedia.cpp @@ -37,8 +37,8 @@ #include "common/scummsys.h" #include "common/util.h" #include "common/config-manager.h" -#include "sound/musicplugin.h" -#include "sound/mpu401.h" +#include "audio/musicplugin.h" +#include "audio/mpu401.h" #include #include diff --git a/backends/midi/seq.cpp b/backends/midi/seq.cpp index 74a92257bc..cc514117fb 100644 --- a/backends/midi/seq.cpp +++ b/backends/midi/seq.cpp @@ -36,8 +36,8 @@ #if defined(USE_SEQ_MIDI) #include "common/util.h" -#include "sound/musicplugin.h" -#include "sound/mpu401.h" +#include "audio/musicplugin.h" +#include "audio/mpu401.h" #include #include diff --git a/backends/midi/stmidi.cpp b/backends/midi/stmidi.cpp index 6e4d69f255..562c3c7ece 100644 --- a/backends/midi/stmidi.cpp +++ b/backends/midi/stmidi.cpp @@ -42,9 +42,9 @@ #if defined __MINT__ #include -#include "sound/mpu401.h" +#include "audio/mpu401.h" #include "common/util.h" -#include "sound/musicplugin.h" +#include "audio/musicplugin.h" class MidiDriver_STMIDI : public MidiDriver_MPU401 { public: diff --git a/backends/midi/timidity.cpp b/backends/midi/timidity.cpp index f0dd24bcbd..4c0cefa156 100644 --- a/backends/midi/timidity.cpp +++ b/backends/midi/timidity.cpp @@ -44,8 +44,8 @@ #include "common/util.h" #include "common/endian.h" #include "common/str.h" -#include "sound/musicplugin.h" -#include "sound/mpu401.h" +#include "audio/musicplugin.h" +#include "audio/mpu401.h" #include #include diff --git a/backends/midi/windows.cpp b/backends/midi/windows.cpp index a50fb4259f..b585b71fbe 100644 --- a/backends/midi/windows.cpp +++ b/backends/midi/windows.cpp @@ -34,8 +34,8 @@ // winnt.h defines ARRAYSIZE, but we want our own one... #undef ARRAYSIZE -#include "sound/musicplugin.h" -#include "sound/mpu401.h" +#include "audio/musicplugin.h" +#include "audio/mpu401.h" #include "common/config-manager.h" #include "common/translation.h" diff --git a/backends/mixer/sdl/sdl-mixer.h b/backends/mixer/sdl/sdl-mixer.h index 42129358aa..afb38f7f36 100644 --- a/backends/mixer/sdl/sdl-mixer.h +++ b/backends/mixer/sdl/sdl-mixer.h @@ -27,7 +27,7 @@ #define BACKENDS_MIXER_SDL_H #include "backends/platform/sdl/sdl-sys.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" /** * SDL mixer manager. It wraps the actual implementation diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp index b790dab036..efcee488a5 100644 --- a/backends/platform/android/android.cpp +++ b/backends/platform/android/android.cpp @@ -56,7 +56,7 @@ #include "backends/saves/default/default-saves.h" #include "backends/timer/default/default-timer.h" #include "backends/plugins/posix/posix-provider.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" #include "backends/platform/android/asset-archive.h" diff --git a/backends/platform/dc/audio.cpp b/backends/platform/dc/audio.cpp index b5667e74b0..ae63186d8c 100644 --- a/backends/platform/dc/audio.cpp +++ b/backends/platform/dc/audio.cpp @@ -25,7 +25,7 @@ #include #include "engines/engine.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" #include "dc.h" EXTERN_C void *memcpy4s(void *s1, const void *s2, unsigned int n); diff --git a/backends/platform/dc/dc.h b/backends/platform/dc/dc.h index f1d7454007..76c4b8cfd2 100644 --- a/backends/platform/dc/dc.h +++ b/backends/platform/dc/dc.h @@ -30,7 +30,7 @@ #include "backends/timer/default/default-timer.h" #include "backends/audiocd/default/default-audiocd.h" #include "backends/fs/fs-factory.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" #define NUM_BUFFERS 4 #define SOUND_BUFFER_SHIFT 3 diff --git a/backends/platform/dc/dcmain.cpp b/backends/platform/dc/dcmain.cpp index 531bfe2fda..bbd4f994f7 100644 --- a/backends/platform/dc/dcmain.cpp +++ b/backends/platform/dc/dcmain.cpp @@ -34,7 +34,7 @@ #include #include "backends/plugins/dc/dc-provider.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" Icon icon; diff --git a/backends/platform/ds/arm9/source/osystem_ds.h b/backends/platform/ds/arm9/source/osystem_ds.h index 27d75e5124..7feed7541f 100644 --- a/backends/platform/ds/arm9/source/osystem_ds.h +++ b/backends/platform/ds/arm9/source/osystem_ds.h @@ -33,7 +33,7 @@ #include "gbampsave.h" #include "backends/saves/default/default-saves.h" #include "backends/timer/default/default-timer.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" #include "graphics/surface.h" #include "graphics/colormasks.h" diff --git a/backends/platform/gph/gph-main.cpp b/backends/platform/gph/gph-main.cpp index efb71040f5..e711135c15 100644 --- a/backends/platform/gph/gph-main.cpp +++ b/backends/platform/gph/gph-main.cpp @@ -43,7 +43,7 @@ #include "backends/saves/default/default-saves.h" #include "backends/timer/default/default-timer.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" #include #include diff --git a/backends/platform/iphone/osys_main.cpp b/backends/platform/iphone/osys_main.cpp index 9dc4e202c4..813adfbc43 100644 --- a/backends/platform/iphone/osys_main.cpp +++ b/backends/platform/iphone/osys_main.cpp @@ -41,8 +41,8 @@ #include "backends/saves/default/default-saves.h" #include "backends/timer/default/default-timer.h" -#include "sound/mixer.h" -#include "sound/mixer_intern.h" +#include "audio/mixer.h" +#include "audio/mixer_intern.h" #include "osys_main.h" diff --git a/backends/platform/iphone/osys_main.h b/backends/platform/iphone/osys_main.h index df01af798a..5e88510980 100644 --- a/backends/platform/iphone/osys_main.h +++ b/backends/platform/iphone/osys_main.h @@ -27,7 +27,7 @@ #include "iphone_common.h" #include "backends/base-backend.h" #include "common/events.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" #include "backends/fs/posix/posix-fs-factory.h" #include "graphics/colormasks.h" diff --git a/backends/platform/n64/osys_n64.h b/backends/platform/n64/osys_n64.h index 4b2164cc9f..5264a8a82e 100644 --- a/backends/platform/n64/osys_n64.h +++ b/backends/platform/n64/osys_n64.h @@ -39,7 +39,7 @@ #include "graphics/colormasks.h" #include "graphics/pixelformat.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" #include #include diff --git a/backends/platform/null/null.cpp b/backends/platform/null/null.cpp index 9b2b2ca78e..772b2b4722 100644 --- a/backends/platform/null/null.cpp +++ b/backends/platform/null/null.cpp @@ -29,7 +29,7 @@ #if defined(USE_NULL_DRIVER) #include "backends/saves/default/default-saves.h" #include "backends/timer/default/default-timer.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" #include "common/scummsys.h" /* diff --git a/backends/platform/openpandora/op-main.cpp b/backends/platform/openpandora/op-main.cpp index 8b181ef3b0..6e64baddc7 100644 --- a/backends/platform/openpandora/op-main.cpp +++ b/backends/platform/openpandora/op-main.cpp @@ -44,7 +44,7 @@ #include "backends/saves/default/default-saves.h" #include "backends/timer/default/default-timer.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" #include #include diff --git a/backends/platform/ps2/systemps2.cpp b/backends/platform/ps2/systemps2.cpp index da5d55b3fc..ab7839e18f 100644 --- a/backends/platform/ps2/systemps2.cpp +++ b/backends/platform/ps2/systemps2.cpp @@ -57,7 +57,7 @@ #include "graphics/surface.h" #include "graphics/font.h" #include "backends/timer/default/default-timer.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" #include "common/events.h" #include "backends/platform/ps2/ps2debug.h" #include "backends/fs/ps2/ps2-fs-factory.h" diff --git a/backends/platform/psp/mp3.cpp b/backends/platform/psp/mp3.cpp index 0e88418f13..54476dabfd 100644 --- a/backends/platform/psp/mp3.cpp +++ b/backends/platform/psp/mp3.cpp @@ -30,7 +30,7 @@ #include "common/singleton.h" #include "common/mutex.h" -#include "sound/audiostream.h" +#include "audio/audiostream.h" #include #include diff --git a/backends/platform/psp/osys_psp.cpp b/backends/platform/psp/osys_psp.cpp index 73a030f5d1..bb7c1ff7d5 100644 --- a/backends/platform/psp/osys_psp.cpp +++ b/backends/platform/psp/osys_psp.cpp @@ -42,7 +42,7 @@ #include "backends/saves/psp/psp-saves.h" #include "backends/timer/default/default-timer.h" #include "graphics/surface.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" //#define __PSP_DEBUG_FUNCS__ /* For debugging function calls */ //#define __PSP_DEBUG_PRINT__ /* For debug printouts */ diff --git a/backends/platform/psp/osys_psp.h b/backends/platform/psp/osys_psp.h index 8d274bb5bd..051449d4a0 100644 --- a/backends/platform/psp/osys_psp.h +++ b/backends/platform/psp/osys_psp.h @@ -29,7 +29,7 @@ #include "common/scummsys.h" #include "graphics/surface.h" #include "graphics/colormasks.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" #include "backends/base-backend.h" #include "backends/fs/psp/psp-fs-factory.h" diff --git a/backends/platform/wii/osystem.h b/backends/platform/wii/osystem.h index 9ed4b16d79..3c992b8bdc 100644 --- a/backends/platform/wii/osystem.h +++ b/backends/platform/wii/osystem.h @@ -37,7 +37,7 @@ #include "backends/timer/default/default-timer.h" #include "graphics/colormasks.h" #include "graphics/surface.h" -#include "sound/mixer_intern.h" +#include "audio/mixer_intern.h" #ifdef __cplusplus extern "C" { diff --git a/backends/platform/wince/wince-sdl.cpp b/backends/platform/wince/wince-sdl.cpp index 02f107fe9e..ade90b2dfb 100644 --- a/backends/platform/wince/wince-sdl.cpp +++ b/backends/platform/wince/wince-sdl.cpp @@ -40,8 +40,8 @@ #include "base/main.h" #include "base/plugins.h" -#include "sound/mixer_intern.h" -#include "sound/fmopl.h" +#include "audio/mixer_intern.h" +#include "audio/fmopl.h" #include "backends/timer/default/default-timer.h" diff --git a/backends/plugins/elf/memory-manager.cpp b/backends/plugins/elf/memory-manager.cpp index 3682a7235d..b2ac497771 100644 --- a/backends/plugins/elf/memory-manager.cpp +++ b/backends/plugins/elf/memory-manager.cpp @@ -28,7 +28,7 @@ #if defined(DYNAMIC_MODULES) && defined(USE_ELF_LOADER) #include "backends/plugins/elf/memory-manager.h" -#include "common/util.h" +#include "common/util.h" #include DECLARE_SINGLETON(ELFMemoryManager); diff --git a/base/main.cpp b/base/main.cpp index 2c432c1af9..8c613b8e85 100644 --- a/base/main.cpp +++ b/base/main.cpp @@ -54,8 +54,8 @@ #include "gui/message.h" #include "gui/error.h" -#include "sound/mididrv.h" -#include "sound/musicplugin.h" /* for music manager */ +#include "audio/mididrv.h" +#include "audio/musicplugin.h" /* for music manager */ #include "backends/keymapper/keymapper.h" diff --git a/base/plugins.cpp b/base/plugins.cpp index 5f01329df8..1db9c0d499 100644 --- a/base/plugins.cpp +++ b/base/plugins.cpp @@ -622,7 +622,7 @@ const EnginePlugin::List &EngineManager::getPlugins() const { // Music plugins -#include "sound/musicplugin.h" +#include "audio/musicplugin.h" DECLARE_SINGLETON(MusicManager); diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index 885704b9c6..fdc05f0ba9 100644 --- a/engines/agi/agi.cpp +++ b/engines/agi/agi.cpp @@ -40,8 +40,8 @@ #include "graphics/cursorman.h" -#include "sound/mididrv.h" -#include "sound/mixer.h" +#include "audio/mididrv.h" +#include "audio/mixer.h" #include "agi/agi.h" #include "agi/graphics.h" diff --git a/engines/agi/preagi.cpp b/engines/agi/preagi.cpp index 1a5698dffc..fe864d7659 100644 --- a/engines/agi/preagi.cpp +++ b/engines/agi/preagi.cpp @@ -27,7 +27,7 @@ #include "common/debug-channels.h" #include "common/random.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" #include "agi/preagi.h" #include "agi/graphics.h" diff --git a/engines/agi/preagi.h b/engines/agi/preagi.h index 45b50f2eed..58e08945b6 100644 --- a/engines/agi/preagi.h +++ b/engines/agi/preagi.h @@ -29,7 +29,7 @@ #include "agi/agi.h" #include "agi/preagi_common.h" -#include "sound/softsynth/pcspk.h" +#include "audio/softsynth/pcspk.h" namespace Agi { diff --git a/engines/agi/sound.h b/engines/agi/sound.h index 63b36e017c..b858493cb8 100644 --- a/engines/agi/sound.h +++ b/engines/agi/sound.h @@ -26,7 +26,7 @@ #ifndef AGI_SOUND_H #define AGI_SOUND_H -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Agi { diff --git a/engines/agi/sound_2gs.h b/engines/agi/sound_2gs.h index 12dede0b69..391646d6ce 100644 --- a/engines/agi/sound_2gs.h +++ b/engines/agi/sound_2gs.h @@ -27,7 +27,7 @@ #define AGI_SOUND_2GS_H #include "common/frac.h" -#include "sound/audiostream.h" +#include "audio/audiostream.h" namespace Agi { diff --git a/engines/agi/sound_coco3.h b/engines/agi/sound_coco3.h index b60f1937cd..a1cc271871 100644 --- a/engines/agi/sound_coco3.h +++ b/engines/agi/sound_coco3.h @@ -26,7 +26,7 @@ #ifndef AGI_SOUND_COCO3_H #define AGI_SOUND_COCO3_H -#include "sound/audiostream.h" +#include "audio/audiostream.h" namespace Agi { diff --git a/engines/agi/sound_midi.cpp b/engines/agi/sound_midi.cpp index 9011ecaa86..47cab0019f 100644 --- a/engines/agi/sound_midi.cpp +++ b/engines/agi/sound_midi.cpp @@ -42,9 +42,9 @@ // MIDI and digital music class -#include "sound/audiostream.h" -#include "sound/mididrv.h" -#include "sound/midiparser.h" +#include "audio/audiostream.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" #include "common/config-manager.h" #include "common/file.h" #include "common/memstream.h" diff --git a/engines/agi/sound_midi.h b/engines/agi/sound_midi.h index 26b75e0d70..059d41208c 100644 --- a/engines/agi/sound_midi.h +++ b/engines/agi/sound_midi.h @@ -28,8 +28,8 @@ #ifndef AGI_SOUND_MIDI_H #define AGI_SOUND_MIDI_H -#include "sound/mididrv.h" -#include "sound/midiparser.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" #include "common/mutex.h" namespace Agi { diff --git a/engines/agi/sound_pcjr.h b/engines/agi/sound_pcjr.h index fe0e762f4e..3aad29d2ce 100644 --- a/engines/agi/sound_pcjr.h +++ b/engines/agi/sound_pcjr.h @@ -26,7 +26,7 @@ #ifndef AGI_SOUND_PCJR_H #define AGI_SOUND_PCJR_H -#include "sound/audiostream.h" +#include "audio/audiostream.h" namespace Agi { diff --git a/engines/agi/sound_sarien.cpp b/engines/agi/sound_sarien.cpp index 4ede50a749..4ed7f8d029 100644 --- a/engines/agi/sound_sarien.cpp +++ b/engines/agi/sound_sarien.cpp @@ -29,7 +29,7 @@ #include "common/random.h" #include "common/str-array.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" #include "agi/agi.h" diff --git a/engines/agi/sound_sarien.h b/engines/agi/sound_sarien.h index 54222ba624..cea396255c 100644 --- a/engines/agi/sound_sarien.h +++ b/engines/agi/sound_sarien.h @@ -26,7 +26,7 @@ #ifndef AGI_SOUND_SARIEN_H #define AGI_SOUND_SARIEN_H -#include "sound/audiostream.h" +#include "audio/audiostream.h" namespace Agi { diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp index d37a627da0..ae95bb0d2b 100644 --- a/engines/agos/agos.cpp +++ b/engines/agos/agos.cpp @@ -41,8 +41,8 @@ #include "graphics/surface.h" -#include "sound/mididrv.h" -#include "sound/mods/protracker.h" +#include "audio/mididrv.h" +#include "audio/mods/protracker.h" namespace AGOS { diff --git a/engines/agos/animation.cpp b/engines/agos/animation.cpp index cdb0fd2626..4c01f6b826 100644 --- a/engines/agos/animation.cpp +++ b/engines/agos/animation.cpp @@ -39,8 +39,8 @@ #include "agos/intern.h" #include "agos/agos.h" -#include "sound/audiostream.h" -#include "sound/decoders/wave.h" +#include "audio/audiostream.h" +#include "audio/decoders/wave.h" #include "gui/message.h" diff --git a/engines/agos/animation.h b/engines/agos/animation.h index ed22ffea5b..77b02ffd13 100644 --- a/engines/agos/animation.h +++ b/engines/agos/animation.h @@ -30,7 +30,7 @@ #include "video/dxa_decoder.h" #include "video/smk_decoder.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace AGOS { diff --git a/engines/agos/midi.h b/engines/agos/midi.h index d76997737a..9c8303ba3c 100644 --- a/engines/agos/midi.h +++ b/engines/agos/midi.h @@ -26,8 +26,8 @@ #ifndef AGOS_MIDI_H #define AGOS_MIDI_H -#include "sound/mididrv.h" -#include "sound/midiparser.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" #include "common/mutex.h" namespace Common { diff --git a/engines/agos/midiparser_s1d.cpp b/engines/agos/midiparser_s1d.cpp index 568135ef8c..156dc6ecaa 100644 --- a/engines/agos/midiparser_s1d.cpp +++ b/engines/agos/midiparser_s1d.cpp @@ -26,8 +26,8 @@ #include "common/debug.h" #include "common/util.h" -#include "sound/mididrv.h" -#include "sound/midiparser.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" namespace AGOS { diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp index 7f11367d0f..fe09e746ff 100644 --- a/engines/agos/res_snd.cpp +++ b/engines/agos/res_snd.cpp @@ -33,9 +33,9 @@ #include "backends/audiocd/audiocd.h" -#include "sound/audiostream.h" -#include "sound/mididrv.h" -#include "sound/mods/protracker.h" +#include "audio/audiostream.h" +#include "audio/mididrv.h" +#include "audio/mods/protracker.h" namespace AGOS { diff --git a/engines/agos/sound.cpp b/engines/agos/sound.cpp index 20deb47be8..35301793bf 100644 --- a/engines/agos/sound.cpp +++ b/engines/agos/sound.cpp @@ -30,14 +30,14 @@ #include "agos/agos.h" #include "agos/sound.h" -#include "sound/audiostream.h" -#include "sound/decoders/flac.h" -#include "sound/mixer.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/voc.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/wave.h" +#include "audio/audiostream.h" +#include "audio/decoders/flac.h" +#include "audio/mixer.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/voc.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/wave.h" namespace AGOS { diff --git a/engines/agos/sound.h b/engines/agos/sound.h index 10ff14d5ac..9739cfb58f 100644 --- a/engines/agos/sound.h +++ b/engines/agos/sound.h @@ -26,8 +26,8 @@ #ifndef AGOS_SOUND_H #define AGOS_SOUND_H -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" #include "agos/intern.h" #include "common/str.h" diff --git a/engines/cine/sound.cpp b/engines/cine/sound.cpp index 23e9db9670..f0c0668e33 100644 --- a/engines/cine/sound.cpp +++ b/engines/cine/sound.cpp @@ -30,10 +30,10 @@ #include "cine/cine.h" #include "cine/sound.h" -#include "sound/audiostream.h" -#include "sound/fmopl.h" -#include "sound/decoders/raw.h" -#include "sound/mods/soundfx.h" +#include "audio/audiostream.h" +#include "audio/fmopl.h" +#include "audio/decoders/raw.h" +#include "audio/mods/soundfx.h" namespace Cine { diff --git a/engines/cine/sound.h b/engines/cine/sound.h index 6982468035..e9a797989e 100644 --- a/engines/cine/sound.h +++ b/engines/cine/sound.h @@ -27,7 +27,7 @@ #define CINE_SOUND_H_ #include "common/util.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Audio { class AudioStream; diff --git a/engines/cruise/sound.cpp b/engines/cruise/sound.cpp index 3cb499c6a4..8a4b1d0d2b 100644 --- a/engines/cruise/sound.cpp +++ b/engines/cruise/sound.cpp @@ -30,9 +30,9 @@ #include "cruise/sound.h" #include "cruise/volume.h" -#include "sound/audiostream.h" -#include "sound/fmopl.h" -#include "sound/mods/soundfx.h" +#include "audio/audiostream.h" +#include "audio/fmopl.h" +#include "audio/mods/soundfx.h" namespace Cruise { diff --git a/engines/cruise/sound.h b/engines/cruise/sound.h index fdea5bed1b..61985ce802 100644 --- a/engines/cruise/sound.h +++ b/engines/cruise/sound.h @@ -26,9 +26,9 @@ #ifndef CRUISE_SOUND_H #define CRUISE_SOUND_H -#include "sound/mididrv.h" -#include "sound/midiparser.h" -#include "sound/mixer.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" +#include "audio/mixer.h" #include "common/config-manager.h" #include "common/serializer.h" diff --git a/engines/draci/music.cpp b/engines/draci/music.cpp index 95b7ae08da..caa82c2cc0 100644 --- a/engines/draci/music.cpp +++ b/engines/draci/music.cpp @@ -25,9 +25,9 @@ // MIDI and digital music class -#include "sound/audiostream.h" -#include "sound/mididrv.h" -#include "sound/midiparser.h" +#include "audio/audiostream.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" #include "common/config-manager.h" #include "common/file.h" diff --git a/engines/draci/music.h b/engines/draci/music.h index 2d3da47e46..f6f3a5ae50 100644 --- a/engines/draci/music.h +++ b/engines/draci/music.h @@ -28,8 +28,8 @@ #ifndef DRACI_MUSIC_H #define DRACI_MUSIC_H -#include "sound/mididrv.h" -#include "sound/midiparser.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" #include "common/mutex.h" namespace Draci { diff --git a/engines/draci/sound.cpp b/engines/draci/sound.cpp index af096256ea..dc8f548d6b 100644 --- a/engines/draci/sound.cpp +++ b/engines/draci/sound.cpp @@ -36,12 +36,12 @@ #include "draci/draci.h" #include "draci/game.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/flac.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/flac.h" namespace Draci { diff --git a/engines/draci/sound.h b/engines/draci/sound.h index 6e9aae1b6e..4f69af7ff0 100644 --- a/engines/draci/sound.h +++ b/engines/draci/sound.h @@ -29,7 +29,7 @@ #include "common/str.h" #include "common/file.h" #include "common/list.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Common { class Archive; diff --git a/engines/drascula/drascula.cpp b/engines/drascula/drascula.cpp index b931eb191c..e84e80ccd3 100644 --- a/engines/drascula/drascula.cpp +++ b/engines/drascula/drascula.cpp @@ -37,7 +37,7 @@ #include "engines/util.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "drascula/drascula.h" #include "drascula/console.h" diff --git a/engines/drascula/drascula.h b/engines/drascula/drascula.h index 4608624614..ac3f86b9ba 100644 --- a/engines/drascula/drascula.h +++ b/engines/drascula/drascula.h @@ -38,7 +38,7 @@ #include "common/system.h" #include "common/util.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "engines/engine.h" diff --git a/engines/drascula/sound.cpp b/engines/drascula/sound.cpp index 094f9ba817..48e7492b11 100644 --- a/engines/drascula/sound.cpp +++ b/engines/drascula/sound.cpp @@ -23,10 +23,10 @@ * */ -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/voc.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/voc.h" #include "common/config-manager.h" diff --git a/engines/engine.cpp b/engines/engine.cpp index b8ddc631df..0d92c1aef1 100644 --- a/engines/engine.cpp +++ b/engines/engine.cpp @@ -48,7 +48,7 @@ #include "gui/message.h" #include "gui/gui-manager.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "graphics/cursorman.h" diff --git a/engines/gob/gob.cpp b/engines/gob/gob.cpp index 4d36933fef..b65bbe0d0f 100644 --- a/engines/gob/gob.cpp +++ b/engines/gob/gob.cpp @@ -32,7 +32,7 @@ #include "base/plugins.h" #include "common/config-manager.h" #include "common/md5.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" #include "gui/gui-manager.h" #include "gui/dialog.h" diff --git a/engines/gob/inter_v2.cpp b/engines/gob/inter_v2.cpp index bb27dd0888..63b2d7f681 100644 --- a/engines/gob/inter_v2.cpp +++ b/engines/gob/inter_v2.cpp @@ -28,8 +28,8 @@ #include "gui/message.h" -#include "sound/mixer.h" -#include "sound/mods/infogrames.h" +#include "audio/mixer.h" +#include "audio/mods/infogrames.h" #include "gob/gob.h" #include "gob/inter.h" diff --git a/engines/gob/sound/adlib.h b/engines/gob/sound/adlib.h index 3f7b46643f..090841b9d6 100644 --- a/engines/gob/sound/adlib.h +++ b/engines/gob/sound/adlib.h @@ -27,9 +27,9 @@ #define GOB_SOUND_ADLIB_H #include "common/mutex.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/fmopl.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/fmopl.h" namespace Gob { diff --git a/engines/gob/sound/bgatmosphere.h b/engines/gob/sound/bgatmosphere.h index aabf6a7e9e..71a2263341 100644 --- a/engines/gob/sound/bgatmosphere.h +++ b/engines/gob/sound/bgatmosphere.h @@ -26,7 +26,7 @@ #ifndef GOB_SOUND_BGATMOSPHERE_H #define GOB_SOUND_BGATMOSPHERE_H -#include "sound/mixer.h" +#include "audio/mixer.h" #include "common/mutex.h" #include "common/random.h" diff --git a/engines/gob/sound/infogrames.h b/engines/gob/sound/infogrames.h index 4a20dceb22..cdcb0a7c39 100644 --- a/engines/gob/sound/infogrames.h +++ b/engines/gob/sound/infogrames.h @@ -26,8 +26,8 @@ #ifndef GOB_SOUND_INFOGRAMES_H #define GOB_SOUND_INFOGRAMES_H -#include "sound/mixer.h" -#include "sound/mods/infogrames.h" +#include "audio/mixer.h" +#include "audio/mods/infogrames.h" namespace Gob { diff --git a/engines/gob/sound/pcspeaker.h b/engines/gob/sound/pcspeaker.h index 8c4fb08021..f83d65a615 100644 --- a/engines/gob/sound/pcspeaker.h +++ b/engines/gob/sound/pcspeaker.h @@ -26,8 +26,8 @@ #ifndef GOB_SOUND_PCSPEAKER_H #define GOB_SOUND_PCSPEAKER_H -#include "sound/mixer.h" -#include "sound/softsynth/pcspk.h" +#include "audio/mixer.h" +#include "audio/softsynth/pcspk.h" namespace Gob { diff --git a/engines/gob/sound/protracker.cpp b/engines/gob/sound/protracker.cpp index 9c7de72c29..445d8f8cfb 100644 --- a/engines/gob/sound/protracker.cpp +++ b/engines/gob/sound/protracker.cpp @@ -25,7 +25,7 @@ #include "common/file.h" -#include "sound/mods/protracker.h" +#include "audio/mods/protracker.h" #include "gob/sound/protracker.h" diff --git a/engines/gob/sound/protracker.h b/engines/gob/sound/protracker.h index 73cae0d510..0397efb3b7 100644 --- a/engines/gob/sound/protracker.h +++ b/engines/gob/sound/protracker.h @@ -26,8 +26,8 @@ #ifndef GOB_SOUND_PROTRACKER_H #define GOB_SOUND_PROTRACKER_H -#include "sound/mixer.h" -#include "sound/audiostream.h" +#include "audio/mixer.h" +#include "audio/audiostream.h" namespace Gob { diff --git a/engines/gob/sound/soundblaster.h b/engines/gob/sound/soundblaster.h index a2dc87267c..ffe46801dc 100644 --- a/engines/gob/sound/soundblaster.h +++ b/engines/gob/sound/soundblaster.h @@ -27,7 +27,7 @@ #define GOB_SOUND_SOUNDBLASTER_H #include "common/mutex.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "gob/sound/soundmixer.h" diff --git a/engines/gob/sound/sounddesc.cpp b/engines/gob/sound/sounddesc.cpp index 8903597bb0..d33ea89147 100644 --- a/engines/gob/sound/sounddesc.cpp +++ b/engines/gob/sound/sounddesc.cpp @@ -25,9 +25,9 @@ #include "common/util.h" #include "common/memstream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/wave.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/wave.h" #include "gob/sound/sounddesc.h" #include "gob/resources.h" diff --git a/engines/gob/sound/soundmixer.cpp b/engines/gob/sound/soundmixer.cpp index 1f5cf2e053..b6082c577c 100644 --- a/engines/gob/sound/soundmixer.cpp +++ b/engines/gob/sound/soundmixer.cpp @@ -26,7 +26,7 @@ #include "gob/sound/soundmixer.h" #include "gob/sound/sounddesc.h" -#include "sound/decoders/raw.h" +#include "audio/decoders/raw.h" namespace Gob { diff --git a/engines/gob/sound/soundmixer.h b/engines/gob/sound/soundmixer.h index 9e66c474e9..5b283b2d84 100644 --- a/engines/gob/sound/soundmixer.h +++ b/engines/gob/sound/soundmixer.h @@ -28,8 +28,8 @@ #include "common/mutex.h" #include "common/frac.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" namespace Gob { diff --git a/engines/groovie/groovie.cpp b/engines/groovie/groovie.cpp index f280da8f56..4a4f5e0de5 100644 --- a/engines/groovie/groovie.cpp +++ b/engines/groovie/groovie.cpp @@ -40,7 +40,7 @@ #include "backends/audiocd/audiocd.h" #include "engines/util.h" #include "graphics/fontman.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Groovie { diff --git a/engines/groovie/music.cpp b/engines/groovie/music.cpp index fd2cc2bec8..011409155c 100644 --- a/engines/groovie/music.cpp +++ b/engines/groovie/music.cpp @@ -31,7 +31,7 @@ #include "common/config-manager.h" #include "common/macresman.h" #include "common/memstream.h" -#include "sound/midiparser.h" +#include "audio/midiparser.h" namespace Groovie { diff --git a/engines/groovie/music.h b/engines/groovie/music.h index 5b5f5bd346..74bb6f98e5 100644 --- a/engines/groovie/music.h +++ b/engines/groovie/music.h @@ -28,7 +28,7 @@ #include "common/array.h" #include "common/mutex.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" class MidiParser; diff --git a/engines/groovie/player.h b/engines/groovie/player.h index bb156d7929..c9f47b8100 100644 --- a/engines/groovie/player.h +++ b/engines/groovie/player.h @@ -27,7 +27,7 @@ #define GROOVIE_PLAYER_H #include "common/system.h" -#include "sound/audiostream.h" +#include "audio/audiostream.h" namespace Groovie { diff --git a/engines/groovie/roq.cpp b/engines/groovie/roq.cpp index 6772a5a40f..540465050d 100644 --- a/engines/groovie/roq.cpp +++ b/engines/groovie/roq.cpp @@ -36,8 +36,8 @@ // Required for the YUV to RGB conversion #include "graphics/conversion.h" #endif -#include "sound/mixer.h" -#include "sound/decoders/raw.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" namespace Groovie { diff --git a/engines/groovie/vdx.cpp b/engines/groovie/vdx.cpp index 3ab8608031..10177dad34 100644 --- a/engines/groovie/vdx.cpp +++ b/engines/groovie/vdx.cpp @@ -29,8 +29,8 @@ #include "groovie/lzss.h" #include "common/debug-channels.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" #define TILE_SIZE 4 // Size of each tile on the image: only ever seen 4 so far #define VDX_IDENT 0x9267 // 37479 diff --git a/engines/hugo/sound.cpp b/engines/hugo/sound.cpp index b382f449b0..ecf1bd1a86 100644 --- a/engines/hugo/sound.cpp +++ b/engines/hugo/sound.cpp @@ -35,8 +35,8 @@ #include "common/system.h" #include "common/config-manager.h" -#include "sound/decoders/raw.h" -#include "sound/audiostream.h" +#include "audio/decoders/raw.h" +#include "audio/audiostream.h" #include "hugo/hugo.h" #include "hugo/game.h" diff --git a/engines/hugo/sound.h b/engines/hugo/sound.h index 0e8905c762..e39f932741 100644 --- a/engines/hugo/sound.h +++ b/engines/hugo/sound.h @@ -33,10 +33,10 @@ #ifndef HUGO_SOUND_H #define HUGO_SOUND_H -#include "sound/mixer.h" -#include "sound/mididrv.h" -#include "sound/midiparser.h" -#include "sound/softsynth/pcspk.h" +#include "audio/mixer.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" +#include "audio/softsynth/pcspk.h" namespace Hugo { diff --git a/engines/kyra/kyra_v1.cpp b/engines/kyra/kyra_v1.cpp index 26c4001578..f5b96410e8 100644 --- a/engines/kyra/kyra_v1.cpp +++ b/engines/kyra/kyra_v1.cpp @@ -27,8 +27,8 @@ #include "common/debug-channels.h" #include "common/EventRecorder.h" -#include "sound/mididrv.h" -#include "sound/mixer.h" +#include "audio/mididrv.h" +#include "audio/mixer.h" #include "kyra/kyra_v1.h" #include "kyra/sound_intern.h" diff --git a/engines/kyra/kyra_v1.h b/engines/kyra/kyra_v1.h index 31c07336a6..12b9fb5942 100644 --- a/engines/kyra/kyra_v1.h +++ b/engines/kyra/kyra_v1.h @@ -34,7 +34,7 @@ #include "common/hashmap.h" #include "common/system.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "kyra/script.h" #include "kyra/item.h" diff --git a/engines/kyra/lol.cpp b/engines/kyra/lol.cpp index 87bd67b40b..36bc0c99d7 100644 --- a/engines/kyra/lol.cpp +++ b/engines/kyra/lol.cpp @@ -34,8 +34,8 @@ #include "kyra/util.h" #include "kyra/debugger.h" -#include "sound/decoders/voc.h" -#include "sound/audiostream.h" +#include "audio/decoders/voc.h" +#include "audio/audiostream.h" #include "common/config-manager.h" #include "common/endian.h" diff --git a/engines/kyra/sound.cpp b/engines/kyra/sound.cpp index a1d88a3005..9dd046e50b 100644 --- a/engines/kyra/sound.cpp +++ b/engines/kyra/sound.cpp @@ -30,14 +30,14 @@ #include "kyra/resource.h" #include "kyra/sound.h" -#include "sound/mixer.h" -#include "sound/decoders/voc.h" -#include "sound/audiostream.h" - -#include "sound/decoders/flac.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/vorbis.h" +#include "audio/mixer.h" +#include "audio/decoders/voc.h" +#include "audio/audiostream.h" + +#include "audio/decoders/flac.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/vorbis.h" namespace Kyra { diff --git a/engines/kyra/sound.h b/engines/kyra/sound.h index 8b83796d46..326fef94d1 100644 --- a/engines/kyra/sound.h +++ b/engines/kyra/sound.h @@ -31,7 +31,7 @@ #include "common/scummsys.h" #include "common/str.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Audio { class AudioStream; diff --git a/engines/kyra/sound_adlib.cpp b/engines/kyra/sound_adlib.cpp index a45972ece7..758d66192d 100644 --- a/engines/kyra/sound_adlib.cpp +++ b/engines/kyra/sound_adlib.cpp @@ -45,9 +45,9 @@ #include "kyra/resource.h" #include "kyra/sound_intern.h" -#include "sound/mixer.h" -#include "sound/fmopl.h" -#include "sound/audiostream.h" +#include "audio/mixer.h" +#include "audio/fmopl.h" +#include "audio/audiostream.h" // Basic AdLib Programming: // http://www.gamedev.net/reference/articles/article446.asp diff --git a/engines/kyra/sound_amiga.cpp b/engines/kyra/sound_amiga.cpp index 5ded3e31e7..c5e9f6171d 100644 --- a/engines/kyra/sound_amiga.cpp +++ b/engines/kyra/sound_amiga.cpp @@ -28,9 +28,9 @@ #include "kyra/resource.h" #include "kyra/sound_intern.h" -#include "sound/mixer.h" -#include "sound/mods/maxtrax.h" -#include "sound/audiostream.h" +#include "audio/mixer.h" +#include "audio/mods/maxtrax.h" +#include "audio/audiostream.h" namespace Kyra { diff --git a/engines/kyra/sound_digital.cpp b/engines/kyra/sound_digital.cpp index 150bafbef3..3d318b7e24 100644 --- a/engines/kyra/sound_digital.cpp +++ b/engines/kyra/sound_digital.cpp @@ -27,11 +27,11 @@ #include "kyra/resource.h" #include "kyra/kyra_mr.h" -#include "sound/audiostream.h" +#include "audio/audiostream.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/flac.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/flac.h" namespace Kyra { diff --git a/engines/kyra/sound_intern.h b/engines/kyra/sound_intern.h index bc1c2be1e2..7b33fc218c 100644 --- a/engines/kyra/sound_intern.h +++ b/engines/kyra/sound_intern.h @@ -31,11 +31,11 @@ #include "common/mutex.h" -#include "sound/softsynth/fmtowns_pc98/towns_pc98_driver.h" -#include "sound/softsynth/fmtowns_pc98/towns_euphony.h" +#include "audio/softsynth/fmtowns_pc98/towns_pc98_driver.h" +#include "audio/softsynth/fmtowns_pc98/towns_euphony.h" -#include "sound/softsynth/emumidi.h" -#include "sound/midiparser.h" +#include "audio/softsynth/emumidi.h" +#include "audio/midiparser.h" namespace Audio { class PCSpeaker; diff --git a/engines/kyra/sound_lol.cpp b/engines/kyra/sound_lol.cpp index c233987120..87679f5bfb 100644 --- a/engines/kyra/sound_lol.cpp +++ b/engines/kyra/sound_lol.cpp @@ -29,7 +29,7 @@ #include "kyra/lol.h" #include "kyra/resource.h" -#include "sound/audiostream.h" +#include "audio/audiostream.h" namespace Kyra { diff --git a/engines/kyra/sound_pcspk.cpp b/engines/kyra/sound_pcspk.cpp index 0f3cce1c55..46f35af3ab 100644 --- a/engines/kyra/sound_pcspk.cpp +++ b/engines/kyra/sound_pcspk.cpp @@ -25,8 +25,8 @@ #include "kyra/sound_intern.h" -#include "sound/mixer.h" -#include "sound/softsynth/pcspk.h" +#include "audio/mixer.h" +#include "audio/softsynth/pcspk.h" namespace Kyra { diff --git a/engines/kyra/sound_towns.cpp b/engines/kyra/sound_towns.cpp index 1bd5af07de..416ad520bb 100644 --- a/engines/kyra/sound_towns.cpp +++ b/engines/kyra/sound_towns.cpp @@ -32,8 +32,8 @@ #include "backends/audiocd/audiocd.h" -#include "sound/audiostream.h" -#include "sound/decoders/raw.h" +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" #include "common/util.h" diff --git a/engines/kyra/vqa.cpp b/engines/kyra/vqa.cpp index 674a5755cf..382ec2429d 100644 --- a/engines/kyra/vqa.cpp +++ b/engines/kyra/vqa.cpp @@ -35,9 +35,9 @@ #include "kyra/vqa.h" #include "common/system.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" #include "kyra/sound.h" #include "kyra/screen.h" diff --git a/engines/kyra/vqa.h b/engines/kyra/vqa.h index 129d526e98..b618b2f044 100644 --- a/engines/kyra/vqa.h +++ b/engines/kyra/vqa.h @@ -28,7 +28,7 @@ #include "common/scummsys.h" -#include "sound/mixer.h" +#include "audio/mixer.h" class OSystem; diff --git a/engines/lastexpress/data/snd.cpp b/engines/lastexpress/data/snd.cpp index b98e490215..bd2320726a 100644 --- a/engines/lastexpress/data/snd.cpp +++ b/engines/lastexpress/data/snd.cpp @@ -30,8 +30,8 @@ #include "lastexpress/debug.h" -#include "sound/decoders/adpcm.h" -#include "sound/audiostream.h" +#include "audio/decoders/adpcm.h" +#include "audio/audiostream.h" #include "common/memstream.h" namespace LastExpress { diff --git a/engines/lastexpress/data/snd.h b/engines/lastexpress/data/snd.h index f76f8ff6ca..2d28404f42 100644 --- a/engines/lastexpress/data/snd.h +++ b/engines/lastexpress/data/snd.h @@ -39,7 +39,7 @@ byte {x} - IMA ADPCM sample codes */ -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Audio { class AudioStream; diff --git a/engines/lure/lure.h b/engines/lure/lure.h index 297fb20f59..99e9e3d93e 100644 --- a/engines/lure/lure.h +++ b/engines/lure/lure.h @@ -28,7 +28,7 @@ #include "engines/engine.h" #include "common/rect.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "common/file.h" #include "common/savefile.h" #include "common/util.h" diff --git a/engines/lure/sound.cpp b/engines/lure/sound.cpp index 85beba8447..5f954eb337 100644 --- a/engines/lure/sound.cpp +++ b/engines/lure/sound.cpp @@ -32,7 +32,7 @@ #include "common/algorithm.h" #include "common/config-manager.h" #include "common/endian.h" -#include "sound/midiparser.h" +#include "audio/midiparser.h" DECLARE_SINGLETON(Lure::SoundManager); diff --git a/engines/lure/sound.h b/engines/lure/sound.h index 6d248fbd20..f50748ab0a 100644 --- a/engines/lure/sound.h +++ b/engines/lure/sound.h @@ -32,8 +32,8 @@ #include "common/singleton.h" #include "common/ptr.h" -#include "sound/mididrv.h" -#include "sound/mixer.h" +#include "audio/mididrv.h" +#include "audio/mixer.h" class MidiParser; diff --git a/engines/m4/m4.cpp b/engines/m4/m4.cpp index 73dba87e99..d557ef991b 100644 --- a/engines/m4/m4.cpp +++ b/engines/m4/m4.cpp @@ -61,7 +61,7 @@ #include "common/debug-channels.h" #include "engines/util.h" #include "graphics/surface.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" namespace M4 { diff --git a/engines/m4/m4.h b/engines/m4/m4.h index 8069c960f1..f665df262b 100644 --- a/engines/m4/m4.h +++ b/engines/m4/m4.h @@ -29,7 +29,7 @@ #include "common/scummsys.h" #include "common/util.h" #include "common/random.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" #include "engines/engine.h" diff --git a/engines/m4/midi.h b/engines/m4/midi.h index 45cf23b58d..8344f8d485 100644 --- a/engines/m4/midi.h +++ b/engines/m4/midi.h @@ -28,8 +28,8 @@ #ifndef M4_MIDI_H #define M4_MIDI_H -#include "sound/mididrv.h" -#include "sound/midiparser.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" #include "common/mutex.h" namespace M4 { diff --git a/engines/m4/sound.cpp b/engines/m4/sound.cpp index 76f0cded46..14c60c1c58 100644 --- a/engines/m4/sound.cpp +++ b/engines/m4/sound.cpp @@ -29,9 +29,9 @@ #include "common/stream.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" namespace M4 { diff --git a/engines/m4/sound.h b/engines/m4/sound.h index 5587810506..8962e67dfd 100644 --- a/engines/m4/sound.h +++ b/engines/m4/sound.h @@ -30,10 +30,10 @@ #include "common/file.h" #include "common/array.h" -#include "sound/mixer.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/flac.h" +#include "audio/mixer.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/flac.h" namespace M4 { diff --git a/engines/made/made.cpp b/engines/made/made.cpp index ba22074bec..11f7734495 100644 --- a/engines/made/made.cpp +++ b/engines/made/made.cpp @@ -40,7 +40,7 @@ #include "base/plugins.h" #include "base/version.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "made/made.h" #include "made/database.h" diff --git a/engines/made/made.h b/engines/made/made.h index 302c8f5275..780f59e442 100644 --- a/engines/made/made.h +++ b/engines/made/made.h @@ -39,9 +39,9 @@ #include "graphics/surface.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/voc.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/voc.h" #include "engines/engine.h" diff --git a/engines/made/music.cpp b/engines/made/music.cpp index 3124fc069e..8c9248a262 100644 --- a/engines/made/music.cpp +++ b/engines/made/music.cpp @@ -27,9 +27,9 @@ // MIDI and digital music class -#include "sound/audiostream.h" -#include "sound/mididrv.h" -#include "sound/midiparser.h" +#include "audio/audiostream.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" #include "common/config-manager.h" #include "common/file.h" diff --git a/engines/made/music.h b/engines/made/music.h index 116ea7d5fd..9840c50cfa 100644 --- a/engines/made/music.h +++ b/engines/made/music.h @@ -28,8 +28,8 @@ #ifndef MADE_MUSIC_H #define MADE_MUSIC_H -#include "sound/mididrv.h" -#include "sound/midiparser.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" #include "common/mutex.h" #include "made/resource.h" diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp index a21d767998..34163a940d 100644 --- a/engines/made/pmvplayer.cpp +++ b/engines/made/pmvplayer.cpp @@ -26,7 +26,7 @@ #include "made/pmvplayer.h" #include "made/screen.h" -#include "sound/decoders/raw.h" +#include "audio/decoders/raw.h" namespace Made { diff --git a/engines/made/pmvplayer.h b/engines/made/pmvplayer.h index 3a5295711d..8014a5a0f1 100644 --- a/engines/made/pmvplayer.h +++ b/engines/made/pmvplayer.h @@ -31,8 +31,8 @@ #include "common/file.h" #include "common/endian.h" #include "graphics/surface.h" -#include "sound/mixer.h" -#include "sound/audiostream.h" +#include "audio/mixer.h" +#include "audio/audiostream.h" #include "made/graphics.h" #include "made/sound.h" diff --git a/engines/made/resource.cpp b/engines/made/resource.cpp index 6140e7e0de..f8582aec40 100644 --- a/engines/made/resource.cpp +++ b/engines/made/resource.cpp @@ -25,8 +25,8 @@ #include "common/endian.h" #include "common/memstream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" #include "made/resource.h" #include "made/graphics.h" diff --git a/engines/made/resource.h b/engines/made/resource.h index ca71dc86f7..5d5664b4c7 100644 --- a/engines/made/resource.h +++ b/engines/made/resource.h @@ -31,7 +31,7 @@ #include "common/stream.h" #include "common/hashmap.h" #include "graphics/surface.h" -#include "sound/audiostream.h" +#include "audio/audiostream.h" #include "made/sound.h" diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp index 1a17beb19b..4f1937c057 100644 --- a/engines/made/scriptfuncs.cpp +++ b/engines/made/scriptfuncs.cpp @@ -29,7 +29,7 @@ #include "backends/audiocd/audiocd.h" #include "graphics/cursorman.h" -#include "sound/softsynth/pcspk.h" +#include "audio/softsynth/pcspk.h" #include "made/made.h" #include "made/resource.h" diff --git a/engines/mohawk/cstime.h b/engines/mohawk/cstime.h index 3b5be08c2c..40293916db 100644 --- a/engines/mohawk/cstime.h +++ b/engines/mohawk/cstime.h @@ -33,7 +33,7 @@ #include "common/random.h" #include "common/list.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Mohawk { diff --git a/engines/mohawk/livingbooks.h b/engines/mohawk/livingbooks.h index 7b072e61de..68b9ac6461 100644 --- a/engines/mohawk/livingbooks.h +++ b/engines/mohawk/livingbooks.h @@ -36,7 +36,7 @@ #include "common/queue.h" #include "common/random.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "livingbooks_code.h" diff --git a/engines/mohawk/mohawk.cpp b/engines/mohawk/mohawk.cpp index fbe52da998..a3db630c6a 100644 --- a/engines/mohawk/mohawk.cpp +++ b/engines/mohawk/mohawk.cpp @@ -38,7 +38,7 @@ #include "mohawk/sound.h" #include "mohawk/video.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Mohawk { diff --git a/engines/mohawk/sound.cpp b/engines/mohawk/sound.cpp index 1583baf2b0..e5528e64f4 100644 --- a/engines/mohawk/sound.cpp +++ b/engines/mohawk/sound.cpp @@ -27,11 +27,11 @@ #include "common/util.h" -#include "sound/musicplugin.h" -#include "sound/audiostream.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/wave.h" +#include "audio/musicplugin.h" +#include "audio/audiostream.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/wave.h" namespace Mohawk { diff --git a/engines/mohawk/sound.h b/engines/mohawk/sound.h index 33fd167d9f..938f15fcb2 100644 --- a/engines/mohawk/sound.h +++ b/engines/mohawk/sound.h @@ -29,11 +29,11 @@ #include "common/scummsys.h" #include "common/str.h" -#include "sound/audiostream.h" -#include "sound/decoders/adpcm.h" -#include "sound/mididrv.h" -#include "sound/midiparser.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/decoders/adpcm.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" +#include "audio/mixer.h" #include "mohawk/mohawk.h" #include "mohawk/resource.h" diff --git a/engines/parallaction/sound.h b/engines/parallaction/sound.h index 1efbb17395..7b3eff37cb 100644 --- a/engines/parallaction/sound.h +++ b/engines/parallaction/sound.h @@ -29,10 +29,10 @@ #include "common/util.h" #include "common/mutex.h" -#include "sound/audiostream.h" -#include "sound/decoders/iff_sound.h" -#include "sound/mixer.h" -#include "sound/mididrv.h" +#include "audio/audiostream.h" +#include "audio/decoders/iff_sound.h" +#include "audio/mixer.h" +#include "audio/mididrv.h" #define PATH_LEN 200 diff --git a/engines/parallaction/sound_br.cpp b/engines/parallaction/sound_br.cpp index 7aaafa8be3..978d809a61 100644 --- a/engines/parallaction/sound_br.cpp +++ b/engines/parallaction/sound_br.cpp @@ -23,15 +23,15 @@ * */ -#include "sound/mixer.h" +#include "audio/mixer.h" #include "common/stream.h" #include "common/util.h" -#include "sound/mixer.h" -#include "sound/mididrv.h" -#include "sound/midiparser.h" -#include "sound/mods/protracker.h" -#include "sound/decoders/raw.h" +#include "audio/mixer.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" +#include "audio/mods/protracker.h" +#include "audio/decoders/raw.h" #include "parallaction/disk.h" #include "parallaction/parallaction.h" diff --git a/engines/parallaction/sound_ns.cpp b/engines/parallaction/sound_ns.cpp index 1ed45befa9..15fdede571 100644 --- a/engines/parallaction/sound_ns.cpp +++ b/engines/parallaction/sound_ns.cpp @@ -27,10 +27,10 @@ #include "common/stream.h" -#include "sound/mixer.h" -#include "sound/midiparser.h" -#include "sound/mods/protracker.h" -#include "sound/decoders/raw.h" +#include "audio/mixer.h" +#include "audio/midiparser.h" +#include "audio/mods/protracker.h" +#include "audio/decoders/raw.h" #include "parallaction/sound.h" #include "parallaction/parallaction.h" diff --git a/engines/queen/midiadlib.cpp b/engines/queen/midiadlib.cpp index 904952b1c5..c3bc6d1c17 100644 --- a/engines/queen/midiadlib.cpp +++ b/engines/queen/midiadlib.cpp @@ -25,8 +25,8 @@ #include "common/endian.h" -#include "sound/fmopl.h" -#include "sound/softsynth/emumidi.h" +#include "audio/fmopl.h" +#include "audio/softsynth/emumidi.h" namespace Queen { diff --git a/engines/queen/music.cpp b/engines/queen/music.cpp index be6b0dc773..4dc698dac9 100644 --- a/engines/queen/music.cpp +++ b/engines/queen/music.cpp @@ -32,7 +32,7 @@ #include "queen/resource.h" #include "queen/sound.h" -#include "sound/midiparser.h" +#include "audio/midiparser.h" namespace Queen { diff --git a/engines/queen/music.h b/engines/queen/music.h index 16c2012973..fe45d56fde 100644 --- a/engines/queen/music.h +++ b/engines/queen/music.h @@ -29,7 +29,7 @@ #include "common/util.h" #include "common/mutex.h" #include "common/random.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" class MidiParser; diff --git a/engines/queen/sound.cpp b/engines/queen/sound.cpp index 503e46fc48..6eb7362338 100644 --- a/engines/queen/sound.cpp +++ b/engines/queen/sound.cpp @@ -35,12 +35,12 @@ #include "queen/queen.h" #include "queen/resource.h" -#include "sound/audiostream.h" -#include "sound/decoders/flac.h" -#include "sound/mididrv.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/vorbis.h" +#include "audio/audiostream.h" +#include "audio/decoders/flac.h" +#include "audio/mididrv.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/vorbis.h" #define SB_HEADER_SIZE_V104 110 #define SB_HEADER_SIZE_V110 122 diff --git a/engines/queen/sound.h b/engines/queen/sound.h index 331034f746..cee0463ed1 100644 --- a/engines/queen/sound.h +++ b/engines/queen/sound.h @@ -27,8 +27,8 @@ #define QUEEN_SOUND_H #include "common/util.h" -#include "sound/mixer.h" -#include "sound/mods/rjp1.h" +#include "audio/mixer.h" +#include "audio/mods/rjp1.h" #include "queen/defs.h" namespace Common { diff --git a/engines/saga/introproc_saga2.cpp b/engines/saga/introproc_saga2.cpp index 2752ddd933..5ae80b14c9 100644 --- a/engines/saga/introproc_saga2.cpp +++ b/engines/saga/introproc_saga2.cpp @@ -31,7 +31,7 @@ #include "saga/scene.h" #include "saga/gfx.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "graphics/surface.h" #include "video/smk_decoder.h" diff --git a/engines/saga/music.cpp b/engines/saga/music.cpp index 3c0ce75db0..dd3034cdb9 100644 --- a/engines/saga/music.cpp +++ b/engines/saga/music.cpp @@ -30,10 +30,10 @@ #include "saga/resource.h" #include "saga/music.h" -#include "sound/audiostream.h" -#include "sound/mididrv.h" -#include "sound/midiparser.h" -#include "sound/decoders/raw.h" +#include "audio/audiostream.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" +#include "audio/decoders/raw.h" #include "common/config-manager.h" #include "common/file.h" #include "common/substream.h" diff --git a/engines/saga/music.h b/engines/saga/music.h index c7fef7225b..7d599d91c2 100644 --- a/engines/saga/music.h +++ b/engines/saga/music.h @@ -28,12 +28,12 @@ #ifndef SAGA_MUSIC_H #define SAGA_MUSIC_H -#include "sound/mididrv.h" -#include "sound/midiparser.h" -#include "sound/mixer.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/flac.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" +#include "audio/mixer.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/flac.h" #include "common/mutex.h" namespace Saga { diff --git a/engines/saga/saga.cpp b/engines/saga/saga.cpp index abd681ce87..2e34b49dc5 100644 --- a/engines/saga/saga.cpp +++ b/engines/saga/saga.cpp @@ -30,7 +30,7 @@ #include "common/events.h" #include "common/EventRecorder.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "saga/saga.h" diff --git a/engines/saga/saga.h b/engines/saga/saga.h index 149f1cb580..52c4b824e7 100644 --- a/engines/saga/saga.h +++ b/engines/saga/saga.h @@ -31,7 +31,7 @@ #include "common/array.h" #include "common/random.h" #include "common/memstream.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" #include "saga/gfx.h" diff --git a/engines/saga/shorten.cpp b/engines/saga/shorten.cpp index a87e427f9a..685cb26aa7 100644 --- a/engines/saga/shorten.cpp +++ b/engines/saga/shorten.cpp @@ -37,8 +37,8 @@ #include "common/endian.h" #include "common/util.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" namespace Saga { diff --git a/engines/saga/shorten.h b/engines/saga/shorten.h index 368d2ac985..f320e1dcc5 100644 --- a/engines/saga/shorten.h +++ b/engines/saga/shorten.h @@ -35,7 +35,7 @@ #include "common/scummsys.h" #include "common/stream.h" -#include "sound/audiostream.h" +#include "audio/audiostream.h" namespace Saga { diff --git a/engines/saga/sndres.cpp b/engines/saga/sndres.cpp index 74fde3e497..46b13727a0 100644 --- a/engines/saga/sndres.cpp +++ b/engines/saga/sndres.cpp @@ -34,12 +34,12 @@ #include "common/file.h" -#include "sound/audiostream.h" -#include "sound/decoders/adpcm.h" -#include "sound/decoders/aiff.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/voc.h" -#include "sound/decoders/wave.h" +#include "audio/audiostream.h" +#include "audio/decoders/adpcm.h" +#include "audio/decoders/aiff.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/voc.h" +#include "audio/decoders/wave.h" #ifdef ENABLE_SAGA2 #include "saga/shorten.h" #endif diff --git a/engines/saga/sound.cpp b/engines/saga/sound.cpp index b3fcf4def3..07e8487ee4 100644 --- a/engines/saga/sound.cpp +++ b/engines/saga/sound.cpp @@ -28,10 +28,10 @@ #include "saga/saga.h" #include "saga/sound.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/adpcm.h" -#include "sound/decoders/raw.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/adpcm.h" +#include "audio/decoders/raw.h" namespace Saga { diff --git a/engines/saga/sound.h b/engines/saga/sound.h index b5f82369f4..15003b515c 100644 --- a/engines/saga/sound.h +++ b/engines/saga/sound.h @@ -29,10 +29,10 @@ #define SAGA_SOUND_H #include "common/file.h" -#include "sound/mixer.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/flac.h" +#include "audio/mixer.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/flac.h" namespace Saga { diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp index f6ec60acb6..e006e22482 100644 --- a/engines/sci/engine/ksound.cpp +++ b/engines/sci/engine/ksound.cpp @@ -31,7 +31,7 @@ #include "sci/sound/audio.h" #include "sci/sound/soundcmd.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Sci { diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp index b376ba947c..73f471b247 100644 --- a/engines/sci/sound/audio.cpp +++ b/engines/sci/sound/audio.cpp @@ -35,14 +35,14 @@ #include "common/memstream.h" #include "common/system.h" -#include "sound/audiostream.h" -#include "sound/decoders/aiff.h" -#include "sound/decoders/flac.h" -#include "sound/decoders/mac_snd.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/wave.h" +#include "audio/audiostream.h" +#include "audio/decoders/aiff.h" +#include "audio/decoders/flac.h" +#include "audio/decoders/mac_snd.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/wave.h" namespace Sci { diff --git a/engines/sci/sound/audio.h b/engines/sci/sound/audio.h index 92d7b946f5..4cc8575a21 100644 --- a/engines/sci/sound/audio.h +++ b/engines/sci/sound/audio.h @@ -28,7 +28,7 @@ #define SCI_AUDIO_H #include "sci/engine/vm_types.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Audio { class RewindableAudioStream; diff --git a/engines/sci/sound/drivers/adlib.cpp b/engines/sci/sound/drivers/adlib.cpp index 20bac4a2c0..057b7c177f 100644 --- a/engines/sci/sound/drivers/adlib.cpp +++ b/engines/sci/sound/drivers/adlib.cpp @@ -27,8 +27,8 @@ #include "common/file.h" -#include "sound/fmopl.h" -#include "sound/softsynth/emumidi.h" +#include "audio/fmopl.h" +#include "audio/softsynth/emumidi.h" #include "sci/resource.h" #include "sci/sound/drivers/mididriver.h" diff --git a/engines/sci/sound/drivers/amigamac.cpp b/engines/sci/sound/drivers/amigamac.cpp index 7cf798a27e..0ec4c283f7 100644 --- a/engines/sci/sound/drivers/amigamac.cpp +++ b/engines/sci/sound/drivers/amigamac.cpp @@ -23,7 +23,7 @@ * */ -#include "sound/softsynth/emumidi.h" +#include "audio/softsynth/emumidi.h" #include "sci/sound/drivers/mididriver.h" #include "sci/resource.h" diff --git a/engines/sci/sound/drivers/cms.cpp b/engines/sci/sound/drivers/cms.cpp index cd7b101f03..ff38e1c554 100644 --- a/engines/sci/sound/drivers/cms.cpp +++ b/engines/sci/sound/drivers/cms.cpp @@ -25,9 +25,9 @@ #include "sci/sound/drivers/mididriver.h" -#include "sound/softsynth/emumidi.h" -#include "sound/softsynth/cms.h" -#include "sound/mixer.h" +#include "audio/softsynth/emumidi.h" +#include "audio/softsynth/cms.h" +#include "audio/mixer.h" #include "sci/resource.h" diff --git a/engines/sci/sound/drivers/midi.cpp b/engines/sci/sound/drivers/midi.cpp index c274c8bce2..b9a973ad7d 100644 --- a/engines/sci/sound/drivers/midi.cpp +++ b/engines/sci/sound/drivers/midi.cpp @@ -29,8 +29,8 @@ #include "common/file.h" #include "common/memstream.h" -#include "sound/fmopl.h" -#include "sound/softsynth/emumidi.h" +#include "audio/fmopl.h" +#include "audio/softsynth/emumidi.h" #include "sci/resource.h" #include "sci/engine/features.h" diff --git a/engines/sci/sound/drivers/mididriver.h b/engines/sci/sound/drivers/mididriver.h index dabe869a8f..f745d62716 100644 --- a/engines/sci/sound/drivers/mididriver.h +++ b/engines/sci/sound/drivers/mididriver.h @@ -27,7 +27,7 @@ #define SCI_SFX_SOFTSEQ_MIDIDRIVER_H #include "sci/sci.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" #include "common/error.h" namespace Sci { diff --git a/engines/sci/sound/drivers/pcjr.cpp b/engines/sci/sound/drivers/pcjr.cpp index e1c3302fef..063332577e 100644 --- a/engines/sci/sound/drivers/pcjr.cpp +++ b/engines/sci/sound/drivers/pcjr.cpp @@ -25,7 +25,7 @@ #include "sci/sound/drivers/mididriver.h" -#include "sound/softsynth/emumidi.h" +#include "audio/softsynth/emumidi.h" namespace Sci { diff --git a/engines/sci/sound/midiparser_sci.h b/engines/sci/sound/midiparser_sci.h index fa24a209e4..39de425e18 100644 --- a/engines/sci/sound/midiparser_sci.h +++ b/engines/sci/sound/midiparser_sci.h @@ -28,7 +28,7 @@ #include "sci/resource.h" #include "sci/sound/music.h" -#include "sound/midiparser.h" +#include "audio/midiparser.h" /* Sound drivers info: (from driver cmd0) diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp index d9be4ed0fe..f0963e7d64 100644 --- a/engines/sci/sound/music.cpp +++ b/engines/sci/sound/music.cpp @@ -23,8 +23,8 @@ * */ -#include "sound/audiostream.h" -#include "sound/decoders/raw.h" +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" #include "common/config-manager.h" #include "sci/sci.h" diff --git a/engines/sci/sound/music.h b/engines/sci/sound/music.h index f735fcd6c5..dfe5ad1586 100644 --- a/engines/sci/sound/music.h +++ b/engines/sci/sound/music.h @@ -29,8 +29,8 @@ #include "common/serializer.h" #include "common/mutex.h" -#include "sound/mixer.h" -#include "sound/audiostream.h" +#include "audio/mixer.h" +#include "audio/audiostream.h" #include "sci/sci.h" #include "sci/resource.h" diff --git a/engines/sci/sound/soundcmd.h b/engines/sci/sound/soundcmd.h index 45b93e9a09..cae72e2cdb 100644 --- a/engines/sci/sound/soundcmd.h +++ b/engines/sci/sound/soundcmd.h @@ -27,7 +27,7 @@ #define SCI_SOUNDCMD_H #include "common/list.h" -#include "sound/mididrv.h" // for MusicType +#include "audio/mididrv.h" // for MusicType #include "sci/engine/state.h" namespace Sci { diff --git a/engines/sci/video/robot_decoder.cpp b/engines/sci/video/robot_decoder.cpp index 8dd60cd9a6..bf52de67d5 100644 --- a/engines/sci/video/robot_decoder.cpp +++ b/engines/sci/video/robot_decoder.cpp @@ -31,7 +31,7 @@ #include "common/util.h" #include "graphics/surface.h" -#include "sound/decoders/raw.h" +#include "audio/decoders/raw.h" #include "sci/resource.h" #include "sci/util.h" diff --git a/engines/sci/video/robot_decoder.h b/engines/sci/video/robot_decoder.h index b8ca0ee857..52bf0bad07 100644 --- a/engines/sci/video/robot_decoder.h +++ b/engines/sci/video/robot_decoder.h @@ -30,8 +30,8 @@ #include "common/rect.h" #include "common/stream.h" #include "common/substream.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" #include "video/video_decoder.h" namespace Sci { diff --git a/engines/scumm/dialogs.cpp b/engines/scumm/dialogs.cpp index 729e66b310..e396d37e30 100644 --- a/engines/scumm/dialogs.cpp +++ b/engines/scumm/dialogs.cpp @@ -46,8 +46,8 @@ #include "scumm/imuse/imuse.h" #include "scumm/imuse_digi/dimuse.h" #include "scumm/verbs.h" -#include "sound/mididrv.h" -#include "sound/mixer.h" +#include "audio/mididrv.h" +#include "audio/mixer.h" #ifndef DISABLE_HELP #include "scumm/help.h" diff --git a/engines/scumm/he/animation_he.cpp b/engines/scumm/he/animation_he.cpp index 80cfcfcd0b..7840b9df20 100644 --- a/engines/scumm/he/animation_he.cpp +++ b/engines/scumm/he/animation_he.cpp @@ -28,7 +28,7 @@ #include "scumm/he/animation_he.h" #include "scumm/he/intern_he.h" -#include "sound/audiostream.h" +#include "audio/audiostream.h" namespace Scumm { diff --git a/engines/scumm/he/animation_he.h b/engines/scumm/he/animation_he.h index 7914d09f40..f5000bca59 100644 --- a/engines/scumm/he/animation_he.h +++ b/engines/scumm/he/animation_he.h @@ -28,7 +28,7 @@ #include "video/smk_decoder.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Scumm { diff --git a/engines/scumm/he/cup_player_he.cpp b/engines/scumm/he/cup_player_he.cpp index 2045b8084c..516ff6b977 100644 --- a/engines/scumm/he/cup_player_he.cpp +++ b/engines/scumm/he/cup_player_he.cpp @@ -27,9 +27,9 @@ #include "common/system.h" #include "common/memstream.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" #include "scumm/scumm.h" #include "scumm/util.h" #include "scumm/he/intern_he.h" diff --git a/engines/scumm/he/resource_he.cpp b/engines/scumm/he/resource_he.cpp index 135af1bb38..72e7052034 100644 --- a/engines/scumm/he/resource_he.cpp +++ b/engines/scumm/he/resource_he.cpp @@ -34,7 +34,7 @@ #include "scumm/he/resource_he.h" #include "scumm/he/sound_he.h" -#include "sound/decoders/wave.h" +#include "audio/decoders/wave.h" #include "graphics/cursorman.h" #include "common/archive.h" diff --git a/engines/scumm/he/sound_he.cpp b/engines/scumm/he/sound_he.cpp index 6feeb2bb8f..4770f28cbc 100644 --- a/engines/scumm/he/sound_he.cpp +++ b/engines/scumm/he/sound_he.cpp @@ -36,15 +36,15 @@ #include "common/timer.h" #include "common/util.h" -#include "sound/decoders/adpcm.h" -#include "sound/decoders/flac.h" -#include "sound/mididrv.h" -#include "sound/mixer.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/voc.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/wave.h" +#include "audio/decoders/adpcm.h" +#include "audio/decoders/flac.h" +#include "audio/mididrv.h" +#include "audio/mixer.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/voc.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/wave.h" namespace Scumm { diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h index 172bcaecd4..0014480894 100644 --- a/engines/scumm/imuse/imuse_internal.h +++ b/engines/scumm/imuse/imuse_internal.h @@ -29,7 +29,7 @@ #include "scumm/imuse/imuse.h" #include "scumm/imuse/instrument.h" #include "scumm/saveload.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" class MidiParser; class OSystem; diff --git a/engines/scumm/imuse/imuse_player.cpp b/engines/scumm/imuse/imuse_player.cpp index a90915e438..ac2b2d4203 100644 --- a/engines/scumm/imuse/imuse_player.cpp +++ b/engines/scumm/imuse/imuse_player.cpp @@ -31,7 +31,7 @@ #include "scumm/saveload.h" #include "scumm/scumm.h" -#include "sound/midiparser.h" +#include "audio/midiparser.h" namespace Scumm { diff --git a/engines/scumm/imuse/instrument.cpp b/engines/scumm/imuse/instrument.cpp index 57a842f297..02996c53d3 100644 --- a/engines/scumm/imuse/instrument.cpp +++ b/engines/scumm/imuse/instrument.cpp @@ -26,7 +26,7 @@ #include "scumm/scumm.h" #include "scumm/saveload.h" #include "scumm/imuse/instrument.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" namespace Scumm { diff --git a/engines/scumm/imuse_digi/dimuse.cpp b/engines/scumm/imuse_digi/dimuse.cpp index ceca557f06..516a0497fa 100644 --- a/engines/scumm/imuse_digi/dimuse.cpp +++ b/engines/scumm/imuse_digi/dimuse.cpp @@ -33,9 +33,9 @@ #include "scumm/imuse_digi/dimuse_bndmgr.h" #include "scumm/imuse_digi/dimuse_track.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" namespace Scumm { diff --git a/engines/scumm/imuse_digi/dimuse.h b/engines/scumm/imuse_digi/dimuse.h index bf5feeb08f..236edb5a89 100644 --- a/engines/scumm/imuse_digi/dimuse.h +++ b/engines/scumm/imuse_digi/dimuse.h @@ -34,8 +34,8 @@ #include "scumm/music.h" #include "scumm/sound.h" -#include "sound/mixer.h" -#include "sound/audiostream.h" +#include "audio/mixer.h" +#include "audio/audiostream.h" namespace Scumm { diff --git a/engines/scumm/imuse_digi/dimuse_script.cpp b/engines/scumm/imuse_digi/dimuse_script.cpp index 06a629072e..02395eed4d 100644 --- a/engines/scumm/imuse_digi/dimuse_script.cpp +++ b/engines/scumm/imuse_digi/dimuse_script.cpp @@ -33,8 +33,8 @@ #include "scumm/imuse_digi/dimuse_bndmgr.h" #include "scumm/imuse_digi/dimuse_track.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" namespace Scumm { diff --git a/engines/scumm/imuse_digi/dimuse_sndmgr.cpp b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp index 4054843163..00074a951a 100644 --- a/engines/scumm/imuse_digi/dimuse_sndmgr.cpp +++ b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp @@ -26,10 +26,10 @@ #include "common/scummsys.h" #include "common/util.h" -#include "sound/decoders/flac.h" -#include "sound/decoders/voc.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/mp3.h" +#include "audio/decoders/flac.h" +#include "audio/decoders/voc.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/mp3.h" #include "scumm/scumm.h" #include "scumm/util.h" diff --git a/engines/scumm/imuse_digi/dimuse_sndmgr.h b/engines/scumm/imuse_digi/dimuse_sndmgr.h index aa7a1b77ca..7ae9fdccb0 100644 --- a/engines/scumm/imuse_digi/dimuse_sndmgr.h +++ b/engines/scumm/imuse_digi/dimuse_sndmgr.h @@ -27,7 +27,7 @@ #include "common/scummsys.h" -#include "sound/audiostream.h" +#include "audio/audiostream.h" #include "scumm/imuse_digi/dimuse_bndmgr.h" namespace Scumm { diff --git a/engines/scumm/imuse_digi/dimuse_track.cpp b/engines/scumm/imuse_digi/dimuse_track.cpp index c56ca40cd0..2a08d56163 100644 --- a/engines/scumm/imuse_digi/dimuse_track.cpp +++ b/engines/scumm/imuse_digi/dimuse_track.cpp @@ -32,8 +32,8 @@ #include "scumm/imuse_digi/dimuse_bndmgr.h" #include "scumm/imuse_digi/dimuse_track.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" namespace Scumm { diff --git a/engines/scumm/midiparser_ro.cpp b/engines/scumm/midiparser_ro.cpp index 2ef4cecfac..e737d747ac 100644 --- a/engines/scumm/midiparser_ro.cpp +++ b/engines/scumm/midiparser_ro.cpp @@ -24,8 +24,8 @@ */ -#include "sound/midiparser.h" -#include "sound/mididrv.h" +#include "audio/midiparser.h" +#include "audio/mididrv.h" #include "common/util.h" namespace Scumm { diff --git a/engines/scumm/player_mod.cpp b/engines/scumm/player_mod.cpp index 930c85dadf..657595fab1 100644 --- a/engines/scumm/player_mod.cpp +++ b/engines/scumm/player_mod.cpp @@ -25,9 +25,9 @@ #include "scumm/player_mod.h" -#include "sound/mixer.h" -#include "sound/rate.h" -#include "sound/decoders/raw.h" +#include "audio/mixer.h" +#include "audio/rate.h" +#include "audio/decoders/raw.h" namespace Scumm { diff --git a/engines/scumm/player_mod.h b/engines/scumm/player_mod.h index 7c0e5916a3..67d1bb4cbf 100644 --- a/engines/scumm/player_mod.h +++ b/engines/scumm/player_mod.h @@ -27,8 +27,8 @@ #define SCUMM_PLAYER_MOD_H #include "scumm/scumm.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" namespace Audio { class RateConverter; diff --git a/engines/scumm/player_nes.cpp b/engines/scumm/player_nes.cpp index 96396e7a08..5f792401e0 100644 --- a/engines/scumm/player_nes.cpp +++ b/engines/scumm/player_nes.cpp @@ -28,7 +28,7 @@ #include "engines/engine.h" #include "scumm/player_nes.h" #include "scumm/scumm.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Scumm { diff --git a/engines/scumm/player_nes.h b/engines/scumm/player_nes.h index 89292f7c24..5f6e8f2d8d 100644 --- a/engines/scumm/player_nes.h +++ b/engines/scumm/player_nes.h @@ -28,8 +28,8 @@ #include "common/scummsys.h" #include "scumm/music.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" namespace Scumm { diff --git a/engines/scumm/player_pce.h b/engines/scumm/player_pce.h index a87b6feb64..3f5033f724 100644 --- a/engines/scumm/player_pce.h +++ b/engines/scumm/player_pce.h @@ -28,8 +28,8 @@ #include "common/scummsys.h" #include "scumm/music.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" // PCE sound engine is only used by Loom, which requires 16bit color support #ifdef USE_RGB_COLOR diff --git a/engines/scumm/player_sid.cpp b/engines/scumm/player_sid.cpp index 35671e094b..34654a68c5 100644 --- a/engines/scumm/player_sid.cpp +++ b/engines/scumm/player_sid.cpp @@ -28,7 +28,7 @@ #include "engines/engine.h" #include "scumm/player_sid.h" #include "scumm/scumm.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Scumm { diff --git a/engines/scumm/player_sid.h b/engines/scumm/player_sid.h index 9e883abe74..71497cc6f4 100644 --- a/engines/scumm/player_sid.h +++ b/engines/scumm/player_sid.h @@ -29,9 +29,9 @@ #include "common/mutex.h" #include "common/scummsys.h" #include "scumm/music.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/softsynth/sid.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/softsynth/sid.h" namespace Scumm { diff --git a/engines/scumm/player_towns.h b/engines/scumm/player_towns.h index e5023d25c2..fda8bd4042 100644 --- a/engines/scumm/player_towns.h +++ b/engines/scumm/player_towns.h @@ -28,7 +28,7 @@ #include "scumm/scumm.h" #include "scumm/imuse/imuse.h" -#include "sound/softsynth/fmtowns_pc98/towns_euphony.h" +#include "audio/softsynth/fmtowns_pc98/towns_euphony.h" namespace Scumm { diff --git a/engines/scumm/player_v2base.h b/engines/scumm/player_v2base.h index 7b90ae98cf..bd910b8a78 100644 --- a/engines/scumm/player_v2base.h +++ b/engines/scumm/player_v2base.h @@ -29,8 +29,8 @@ #include "common/scummsys.h" #include "common/mutex.h" #include "scumm/music.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" namespace Scumm { diff --git a/engines/scumm/player_v2cms.cpp b/engines/scumm/player_v2cms.cpp index 7bec171173..b081a68428 100644 --- a/engines/scumm/player_v2cms.cpp +++ b/engines/scumm/player_v2cms.cpp @@ -25,9 +25,9 @@ #include "scumm/player_v2cms.h" #include "scumm/scumm.h" -#include "sound/mididrv.h" -#include "sound/mixer.h" -#include "sound/softsynth/cms.h" +#include "audio/mididrv.h" +#include "audio/mixer.h" +#include "audio/softsynth/cms.h" namespace Scumm { diff --git a/engines/scumm/player_v4a.h b/engines/scumm/player_v4a.h index abacd8e511..108dbcf317 100644 --- a/engines/scumm/player_v4a.h +++ b/engines/scumm/player_v4a.h @@ -28,8 +28,8 @@ #include "common/scummsys.h" #include "scumm/music.h" -#include "sound/mixer.h" -#include "sound/mods/tfmx.h" +#include "audio/mixer.h" +#include "audio/mods/tfmx.h" class Mixer; diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp index 7273f9f871..bdae4c5f6a 100644 --- a/engines/scumm/saveload.cpp +++ b/engines/scumm/saveload.cpp @@ -44,7 +44,7 @@ #include "scumm/he/sprite_he.h" #include "scumm/verbs.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "backends/audiocd/audiocd.h" diff --git a/engines/scumm/script_v8.cpp b/engines/scumm/script_v8.cpp index f2addff19f..1250a82d33 100644 --- a/engines/scumm/script_v8.cpp +++ b/engines/scumm/script_v8.cpp @@ -39,7 +39,7 @@ #include "scumm/verbs.h" #include "scumm/smush/smush_player.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Scumm { diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index e6110ee976..83df53d8d0 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -77,7 +77,7 @@ #include "backends/audiocd/audiocd.h" -#include "sound/mixer.h" +#include "audio/mixer.h" using Common::File; diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h index 1f083db4a0..587d1be776 100644 --- a/engines/scumm/scumm.h +++ b/engines/scumm/scumm.h @@ -42,7 +42,7 @@ #include "scumm/detection.h" #include "scumm/script.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" #ifdef __DS__ /* This disables the dual layer mode which is used in FM-Towns versions diff --git a/engines/scumm/smush/smush_mixer.cpp b/engines/scumm/smush/smush_mixer.cpp index eca1ebdcab..4ab0d7c733 100644 --- a/engines/scumm/smush/smush_mixer.cpp +++ b/engines/scumm/smush/smush_mixer.cpp @@ -32,8 +32,8 @@ #include "scumm/sound.h" #include "scumm/imuse/imuse.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" namespace Scumm { diff --git a/engines/scumm/smush/smush_player.cpp b/engines/scumm/smush/smush_player.cpp index 29dbb57670..ad8a135a7c 100644 --- a/engines/scumm/smush/smush_player.cpp +++ b/engines/scumm/smush/smush_player.cpp @@ -49,10 +49,10 @@ #include "scumm/insane/insane.h" -#include "sound/mixer.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/vorbis.h" +#include "audio/mixer.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/vorbis.h" #include "common/zlib.h" diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index ee8825d946..cff1618c18 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -38,15 +38,15 @@ #include "backends/audiocd/audiocd.h" -#include "sound/decoders/adpcm.h" -#include "sound/decoders/flac.h" -#include "sound/mididrv.h" -#include "sound/mixer.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/voc.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/wave.h" +#include "audio/decoders/adpcm.h" +#include "audio/decoders/flac.h" +#include "audio/mididrv.h" +#include "audio/mixer.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/voc.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/wave.h" namespace Scumm { diff --git a/engines/scumm/sound.h b/engines/scumm/sound.h index 4fe46f32f0..39b4af805d 100644 --- a/engines/scumm/sound.h +++ b/engines/scumm/sound.h @@ -26,8 +26,8 @@ #define SCUMM_SOUND_H #include "common/scummsys.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" #include "scumm/saveload.h" namespace Audio { diff --git a/engines/sky/intro.cpp b/engines/sky/intro.cpp index 68e84d616d..129de9c129 100644 --- a/engines/sky/intro.cpp +++ b/engines/sky/intro.cpp @@ -38,8 +38,8 @@ #include "sky/struc.h" #include "sky/text.h" -#include "sound/audiostream.h" -#include "sound/decoders/raw.h" +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" namespace Sky { diff --git a/engines/sky/intro.h b/engines/sky/intro.h index 213361eae5..d7a55b95bb 100644 --- a/engines/sky/intro.h +++ b/engines/sky/intro.h @@ -28,7 +28,7 @@ #include "common/scummsys.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Sky { diff --git a/engines/sky/music/adlibchannel.h b/engines/sky/music/adlibchannel.h index 628ceabdb9..cbcf722e4e 100644 --- a/engines/sky/music/adlibchannel.h +++ b/engines/sky/music/adlibchannel.h @@ -27,7 +27,7 @@ #define SKY_MUSIC_ADLIBCHANNEL_H #include "sky/music/musicbase.h" -#include "sound/fmopl.h" +#include "audio/fmopl.h" namespace Sky { diff --git a/engines/sky/music/adlibmusic.cpp b/engines/sky/music/adlibmusic.cpp index 0b593f555b..da46964ef9 100644 --- a/engines/sky/music/adlibmusic.cpp +++ b/engines/sky/music/adlibmusic.cpp @@ -28,7 +28,7 @@ #include "sky/music/adlibmusic.h" #include "sky/music/adlibchannel.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "sky/sky.h" namespace Sky { diff --git a/engines/sky/music/adlibmusic.h b/engines/sky/music/adlibmusic.h index f3356a91a8..848c8eaf29 100644 --- a/engines/sky/music/adlibmusic.h +++ b/engines/sky/music/adlibmusic.h @@ -27,9 +27,9 @@ #define SKY_MUSIC_ADLIBMUSIC_H #include "sky/music/musicbase.h" -#include "sound/audiostream.h" -#include "sound/fmopl.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/fmopl.h" +#include "audio/mixer.h" namespace Sky { diff --git a/engines/sky/music/gmchannel.cpp b/engines/sky/music/gmchannel.cpp index e589642458..3852a69068 100644 --- a/engines/sky/music/gmchannel.cpp +++ b/engines/sky/music/gmchannel.cpp @@ -25,7 +25,7 @@ #include "gmchannel.h" #include "common/util.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" namespace Sky { diff --git a/engines/sky/music/gmmusic.cpp b/engines/sky/music/gmmusic.cpp index e7b3a24170..22964f3f56 100644 --- a/engines/sky/music/gmmusic.cpp +++ b/engines/sky/music/gmmusic.cpp @@ -28,7 +28,7 @@ #include "sky/sky.h" #include "common/util.h" #include "common/endian.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" namespace Sky { diff --git a/engines/sky/music/mt32music.cpp b/engines/sky/music/mt32music.cpp index bdefa66709..51df95e451 100644 --- a/engines/sky/music/mt32music.cpp +++ b/engines/sky/music/mt32music.cpp @@ -28,7 +28,7 @@ #include "common/util.h" #include "common/system.h" #include "common/endian.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" namespace Sky { diff --git a/engines/sky/sky.cpp b/engines/sky/sky.cpp index 30f67bf5ef..14d57465af 100644 --- a/engines/sky/sky.cpp +++ b/engines/sky/sky.cpp @@ -44,8 +44,8 @@ #include "sky/text.h" #include "sky/compact.h" -#include "sound/mididrv.h" -#include "sound/mixer.h" +#include "audio/mididrv.h" +#include "audio/mixer.h" #include "engines/util.h" diff --git a/engines/sky/sound.cpp b/engines/sky/sound.cpp index dc76cb8f0d..3d247ed334 100644 --- a/engines/sky/sound.cpp +++ b/engines/sky/sound.cpp @@ -33,8 +33,8 @@ #include "sky/sound.h" #include "sky/struc.h" -#include "sound/audiostream.h" -#include "sound/decoders/raw.h" +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" namespace Sky { diff --git a/engines/sky/sound.h b/engines/sky/sound.h index 2aecd6ab7f..ce060ce689 100644 --- a/engines/sky/sound.h +++ b/engines/sky/sound.h @@ -28,7 +28,7 @@ #include "common/scummsys.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Sky { diff --git a/engines/sword1/animation.cpp b/engines/sword1/animation.cpp index de35b8122c..fa876b565f 100644 --- a/engines/sword1/animation.cpp +++ b/engines/sword1/animation.cpp @@ -27,7 +27,7 @@ #include "sword1/sword1.h" #include "sword1/animation.h" #include "sword1/text.h" -#include "sound/decoders/vorbis.h" +#include "audio/decoders/vorbis.h" #include "common/config-manager.h" #include "common/endian.h" diff --git a/engines/sword1/animation.h b/engines/sword1/animation.h index 566ca31c2f..7bfd839826 100644 --- a/engines/sword1/animation.h +++ b/engines/sword1/animation.h @@ -32,7 +32,7 @@ #include "common/array.h" -#include "sound/audiostream.h" +#include "audio/audiostream.h" #include "sword1/screen.h" #include "sword1/sound.h" diff --git a/engines/sword1/logic.h b/engines/sword1/logic.h index 4ec8ad15c5..989b852e77 100644 --- a/engines/sword1/logic.h +++ b/engines/sword1/logic.h @@ -31,7 +31,7 @@ #include "sword1/objectman.h" #include "common/util.h" #include "common/random.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Sword1 { diff --git a/engines/sword1/music.cpp b/engines/sword1/music.cpp index d860e1c69d..2b81ae1eb4 100644 --- a/engines/sword1/music.cpp +++ b/engines/sword1/music.cpp @@ -32,14 +32,14 @@ #include "sword1/sword1.h" #include "sword1/music.h" -#include "sound/mixer.h" -#include "sound/audiostream.h" -#include "sound/decoders/aiff.h" -#include "sound/decoders/flac.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/wave.h" -#include "sound/decoders/vag.h" +#include "audio/mixer.h" +#include "audio/audiostream.h" +#include "audio/decoders/aiff.h" +#include "audio/decoders/flac.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/wave.h" +#include "audio/decoders/vag.h" #define SMP_BUFSIZE 8192 diff --git a/engines/sword1/music.h b/engines/sword1/music.h index 6487a1c3e5..7a3d1e5de6 100644 --- a/engines/sword1/music.h +++ b/engines/sword1/music.h @@ -29,9 +29,9 @@ #include "common/scummsys.h" #include "common/mutex.h" #include "common/file.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/rate.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/rate.h" namespace Sword1 { diff --git a/engines/sword1/sound.cpp b/engines/sword1/sound.cpp index ff2f1060ee..f8219eb21a 100644 --- a/engines/sword1/sound.cpp +++ b/engines/sword1/sound.cpp @@ -37,13 +37,13 @@ #include "sword1/logic.h" #include "sword1/sword1.h" -#include "sound/audiostream.h" -#include "sound/decoders/flac.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/wave.h" -#include "sound/decoders/vag.h" +#include "audio/audiostream.h" +#include "audio/decoders/flac.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/wave.h" +#include "audio/decoders/vag.h" namespace Sword1 { diff --git a/engines/sword1/sound.h b/engines/sword1/sound.h index ececb95875..ae96132241 100644 --- a/engines/sword1/sound.h +++ b/engines/sword1/sound.h @@ -31,7 +31,7 @@ #include "common/file.h" #include "common/util.h" #include "common/random.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Audio { class Mixer; diff --git a/engines/sword2/animation.h b/engines/sword2/animation.h index b90726537c..550ac0fac4 100644 --- a/engines/sword2/animation.h +++ b/engines/sword2/animation.h @@ -31,7 +31,7 @@ #include "video/dxa_decoder.h" #include "video/smk_decoder.h" #include "video/video_decoder.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "sword2/screen.h" diff --git a/engines/sword2/music.cpp b/engines/sword2/music.cpp index 1519c5d73f..aa91843432 100644 --- a/engines/sword2/music.cpp +++ b/engines/sword2/music.cpp @@ -37,12 +37,12 @@ #include "common/substream.h" #include "common/system.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/flac.h" -#include "sound/decoders/wave.h" -#include "sound/decoders/vag.h" -#include "sound/rate.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/flac.h" +#include "audio/decoders/wave.h" +#include "audio/decoders/vag.h" +#include "audio/rate.h" #include "sword2/sword2.h" #include "sword2/defs.h" diff --git a/engines/sword2/sound.cpp b/engines/sword2/sound.cpp index abacbd16f0..261c8293bd 100644 --- a/engines/sword2/sound.cpp +++ b/engines/sword2/sound.cpp @@ -48,8 +48,8 @@ #include "sword2/resman.h" #include "sword2/sound.h" -#include "sound/decoders/wave.h" -#include "sound/decoders/vag.h" +#include "audio/decoders/wave.h" +#include "audio/decoders/vag.h" #define Debug_Printf _vm->_debugger->DebugPrintf diff --git a/engines/sword2/sound.h b/engines/sword2/sound.h index 5acc39179f..2c4c8364c7 100644 --- a/engines/sword2/sound.h +++ b/engines/sword2/sound.h @@ -39,8 +39,8 @@ #define SWORD2_SOUND_H #include "common/file.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" // Max number of sound fx #define MAXMUS 2 diff --git a/engines/sword25/fmv/theora_decoder.cpp b/engines/sword25/fmv/theora_decoder.cpp index b72542f95a..e9901c04b0 100644 --- a/engines/sword25/fmv/theora_decoder.cpp +++ b/engines/sword25/fmv/theora_decoder.cpp @@ -42,7 +42,7 @@ #ifdef USE_THEORADEC #include "common/system.h" #include "graphics/conversion.h" -#include "sound/decoders/raw.h" +#include "audio/decoders/raw.h" #include "sword25/kernel/common.h" namespace Sword25 { diff --git a/engines/sword25/fmv/theora_decoder.h b/engines/sword25/fmv/theora_decoder.h index 5e2675b629..a753bc58ac 100644 --- a/engines/sword25/fmv/theora_decoder.h +++ b/engines/sword25/fmv/theora_decoder.h @@ -31,8 +31,8 @@ #ifdef USE_THEORADEC #include "video/video_decoder.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" #include #include diff --git a/engines/sword25/sfx/soundengine.cpp b/engines/sword25/sfx/soundengine.cpp index f28ad26fe7..03cdaf82b7 100644 --- a/engines/sword25/sfx/soundengine.cpp +++ b/engines/sword25/sfx/soundengine.cpp @@ -37,7 +37,7 @@ #include "sword25/package/packagemanager.h" #include "sword25/kernel/resource.h" -#include "sound/decoders/vorbis.h" +#include "audio/decoders/vorbis.h" namespace Sword25 { diff --git a/engines/sword25/sfx/soundengine.h b/engines/sword25/sfx/soundengine.h index 29b3043a68..576582b478 100644 --- a/engines/sword25/sfx/soundengine.h +++ b/engines/sword25/sfx/soundengine.h @@ -52,8 +52,8 @@ #include "sword25/kernel/resservice.h" #include "sword25/kernel/persistable.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" namespace Sword25 { diff --git a/engines/teenagent/music.h b/engines/teenagent/music.h index c1f5765a55..52a347bf10 100644 --- a/engines/teenagent/music.h +++ b/engines/teenagent/music.h @@ -26,7 +26,7 @@ #ifndef TEEN_MUSIC_H #define TEEN_MUSIC_H -#include "sound/mods/paula.h" +#include "audio/mods/paula.h" #include "common/array.h" namespace TeenAgent { diff --git a/engines/teenagent/teenagent.cpp b/engines/teenagent/teenagent.cpp index 523c0e1700..a492237adb 100644 --- a/engines/teenagent/teenagent.cpp +++ b/engines/teenagent/teenagent.cpp @@ -33,8 +33,8 @@ #include "engines/advancedDetector.h" #include "engines/util.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" #include "graphics/cursorman.h" #include "graphics/thumbnail.h" diff --git a/engines/teenagent/teenagent.h b/engines/teenagent/teenagent.h index 7c70866285..a43ee68f56 100644 --- a/engines/teenagent/teenagent.h +++ b/engines/teenagent/teenagent.h @@ -29,8 +29,8 @@ #include "teenagent/pack.h" #include "teenagent/resources.h" #include "teenagent/inventory.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" #include "common/random.h" struct ADGameDescription; diff --git a/engines/testbed/midi.cpp b/engines/testbed/midi.cpp index 499c44e50a..673f5d8547 100644 --- a/engines/testbed/midi.cpp +++ b/engines/testbed/midi.cpp @@ -28,7 +28,7 @@ #include "graphics/cursorman.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" #include "testbed/midi.h" #include "testbed/testbed.h" diff --git a/engines/testbed/midi.h b/engines/testbed/midi.h index 0fb5cf80d5..676229570e 100644 --- a/engines/testbed/midi.h +++ b/engines/testbed/midi.h @@ -26,7 +26,7 @@ #define TESTBED_MIDI_H #include "common/stream.h" -#include "sound/midiparser.h" +#include "audio/midiparser.h" #include "testbed/testsuite.h" // This file can be used as template for header files of other newer testsuites. diff --git a/engines/testbed/sound.cpp b/engines/testbed/sound.cpp index 8354e7bad0..76f9e850c0 100644 --- a/engines/testbed/sound.cpp +++ b/engines/testbed/sound.cpp @@ -22,7 +22,7 @@ * $Id$ */ -#include "sound/softsynth/pcspk.h" +#include "audio/softsynth/pcspk.h" #include "backends/audiocd/audiocd.h" diff --git a/engines/testbed/sound.h b/engines/testbed/sound.h index 24dcf45b99..b797ecb2b0 100644 --- a/engines/testbed/sound.h +++ b/engines/testbed/sound.h @@ -26,7 +26,7 @@ #define TESTBED_SOUND_H #include "gui/dialog.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "testbed/config.h" #include "testbed/testsuite.h" diff --git a/engines/tinsel/bmv.cpp b/engines/tinsel/bmv.cpp index f74f3e34c3..3f56288aca 100644 --- a/engines/tinsel/bmv.cpp +++ b/engines/tinsel/bmv.cpp @@ -42,7 +42,7 @@ #include "tinsel/tinlib.h" #include "tinsel/tinsel.h" -#include "sound/decoders/raw.h" +#include "audio/decoders/raw.h" namespace Tinsel { diff --git a/engines/tinsel/bmv.h b/engines/tinsel/bmv.h index a0359d497d..2644504cab 100644 --- a/engines/tinsel/bmv.h +++ b/engines/tinsel/bmv.h @@ -29,8 +29,8 @@ #include "common/file.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" #include "tinsel/coroutine.h" #include "tinsel/object.h" diff --git a/engines/tinsel/config.cpp b/engines/tinsel/config.cpp index 43e54dc4ea..a206ebf03b 100644 --- a/engines/tinsel/config.cpp +++ b/engines/tinsel/config.cpp @@ -34,7 +34,7 @@ #include "common/file.h" #include "common/config-manager.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Tinsel { diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp index 23ef28a99c..29d4dbc92d 100644 --- a/engines/tinsel/music.cpp +++ b/engines/tinsel/music.cpp @@ -27,10 +27,10 @@ // MIDI and digital music class -#include "sound/audiostream.h" -#include "sound/mididrv.h" -#include "sound/midiparser.h" -#include "sound/decoders/adpcm.h" +#include "audio/audiostream.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" +#include "audio/decoders/adpcm.h" #include "backends/audiocd/audiocd.h" diff --git a/engines/tinsel/music.h b/engines/tinsel/music.h index 069c03c979..e2a3b4c2c1 100644 --- a/engines/tinsel/music.h +++ b/engines/tinsel/music.h @@ -28,10 +28,10 @@ #ifndef TINSEL_MUSIC_H #define TINSEL_MUSIC_H -#include "sound/mididrv.h" -#include "sound/midiparser.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" #include "common/mutex.h" namespace Tinsel { diff --git a/engines/tinsel/sound.cpp b/engines/tinsel/sound.cpp index 7fab3624a4..5fc2820986 100644 --- a/engines/tinsel/sound.cpp +++ b/engines/tinsel/sound.cpp @@ -40,13 +40,13 @@ #include "common/memstream.h" #include "common/system.h" -#include "sound/mixer.h" -#include "sound/decoders/adpcm.h" -#include "sound/decoders/flac.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/vag.h" -#include "sound/decoders/vorbis.h" +#include "audio/mixer.h" +#include "audio/decoders/adpcm.h" +#include "audio/decoders/flac.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/vag.h" +#include "audio/decoders/vorbis.h" #include "gui/message.h" diff --git a/engines/tinsel/sound.h b/engines/tinsel/sound.h index b8990f0106..2b0ef4599e 100644 --- a/engines/tinsel/sound.h +++ b/engines/tinsel/sound.h @@ -30,7 +30,7 @@ #include "common/file.h" #include "common/file.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "tinsel/dw.h" #include "tinsel/tinsel.h" diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h index b4f77d18f2..009f6fe26d 100644 --- a/engines/tinsel/tinsel.h +++ b/engines/tinsel/tinsel.h @@ -34,8 +34,8 @@ #include "common/random.h" #include "common/util.h" -#include "sound/mididrv.h" -#include "sound/mixer.h" +#include "audio/mididrv.h" +#include "audio/mixer.h" #include "engines/engine.h" #include "tinsel/debugger.h" diff --git a/engines/toon/audio.h b/engines/toon/audio.h index e8c6c44e03..7c1eedfee9 100644 --- a/engines/toon/audio.h +++ b/engines/toon/audio.h @@ -26,8 +26,8 @@ #ifndef TOON_AUDIO_H #define TOON_AUDIO_H -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" #include "toon/toon.h" namespace Toon { diff --git a/engines/touche/midi.cpp b/engines/touche/midi.cpp index ee7602abf7..0b15643767 100644 --- a/engines/touche/midi.cpp +++ b/engines/touche/midi.cpp @@ -26,7 +26,7 @@ #include "common/config-manager.h" #include "common/stream.h" -#include "sound/midiparser.h" +#include "audio/midiparser.h" #include "touche/midi.h" diff --git a/engines/touche/midi.h b/engines/touche/midi.h index a518a4bb29..8e43c12037 100644 --- a/engines/touche/midi.h +++ b/engines/touche/midi.h @@ -29,7 +29,7 @@ #include "common/util.h" #include "common/mutex.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" class MidiParser; diff --git a/engines/touche/resource.cpp b/engines/touche/resource.cpp index f76084d556..df7992f26c 100644 --- a/engines/touche/resource.cpp +++ b/engines/touche/resource.cpp @@ -25,13 +25,13 @@ -#include "sound/decoders/flac.h" -#include "sound/mixer.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/voc.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/raw.h" -#include "sound/audiostream.h" +#include "audio/decoders/flac.h" +#include "audio/mixer.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/voc.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/raw.h" +#include "audio/audiostream.h" #include "touche/midi.h" #include "touche/touche.h" diff --git a/engines/touche/touche.cpp b/engines/touche/touche.cpp index e8d9be87d1..ff8b0d944e 100644 --- a/engines/touche/touche.cpp +++ b/engines/touche/touche.cpp @@ -34,7 +34,7 @@ #include "engines/util.h" #include "graphics/cursorman.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" #include "touche/midi.h" #include "touche/touche.h" diff --git a/engines/touche/touche.h b/engines/touche/touche.h index 926dab04b2..50a5c6e439 100644 --- a/engines/touche/touche.h +++ b/engines/touche/touche.h @@ -33,7 +33,7 @@ #include "common/rect.h" #include "common/util.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "engines/engine.h" diff --git a/engines/tucker/resource.cpp b/engines/tucker/resource.cpp index 1d1daf5adc..6b6ab1e600 100644 --- a/engines/tucker/resource.cpp +++ b/engines/tucker/resource.cpp @@ -25,11 +25,11 @@ #include "common/file.h" -#include "sound/audiostream.h" -#include "sound/decoders/flac.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/vorbis.h" -#include "sound/decoders/wave.h" +#include "audio/audiostream.h" +#include "audio/decoders/flac.h" +#include "audio/decoders/mp3.h" +#include "audio/decoders/vorbis.h" +#include "audio/decoders/wave.h" #include "tucker/tucker.h" #include "tucker/graphics.h" diff --git a/engines/tucker/sequences.cpp b/engines/tucker/sequences.cpp index 4a7a5f16d7..73018b8c00 100644 --- a/engines/tucker/sequences.cpp +++ b/engines/tucker/sequences.cpp @@ -25,9 +25,9 @@ #include "common/system.h" -#include "sound/audiostream.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/wave.h" +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/wave.h" #include "tucker/tucker.h" #include "tucker/graphics.h" diff --git a/engines/tucker/tucker.h b/engines/tucker/tucker.h index feb63c301b..4d7b530bbc 100644 --- a/engines/tucker/tucker.h +++ b/engines/tucker/tucker.h @@ -35,7 +35,7 @@ #include "video/flic_decoder.h" -#include "sound/mixer.h" +#include "audio/mixer.h" #include "engines/engine.h" diff --git a/gui/launcher.cpp b/gui/launcher.cpp index 49133002b6..1d209ce71a 100644 --- a/gui/launcher.cpp +++ b/gui/launcher.cpp @@ -49,7 +49,7 @@ #include "graphics/cursorman.h" -#include "sound/mididrv.h" +#include "audio/mididrv.h" using Common::ConfigManager; diff --git a/gui/options.cpp b/gui/options.cpp index 59346da967..a540fbb9d1 100644 --- a/gui/options.cpp +++ b/gui/options.cpp @@ -39,10 +39,10 @@ #include "graphics/scaler.h" -#include "sound/mididrv.h" -#include "sound/musicplugin.h" -#include "sound/mixer.h" -#include "sound/fmopl.h" +#include "audio/mididrv.h" +#include "audio/musicplugin.h" +#include "audio/mixer.h" +#include "audio/fmopl.h" namespace GUI { diff --git a/gui/options.h b/gui/options.h index 1c105046bb..eba1779b69 100644 --- a/gui/options.h +++ b/gui/options.h @@ -27,7 +27,7 @@ #include "gui/dialog.h" #include "common/str.h" -#include "sound/musicplugin.h" +#include "audio/musicplugin.h" #ifdef SMALL_SCREEN_DEVICE #include "gui/KeysDialog.h" diff --git a/po/POTFILES b/po/POTFILES index 48e2e58d3e..8ba37f7bf3 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -30,17 +30,17 @@ engines/cruise/menu.cpp engines/sci/engine/kfile.cpp engines/agos/saveload.cpp -sound/fmopl.cpp -sound/musicplugin.cpp -sound/null.h -sound/null.cpp -sound/mods/paula.cpp -sound/softsynth/adlib.cpp -sound/softsynth/appleiigs.cpp -sound/softsynth/sid.cpp -sound/softsynth/mt32.cpp -sound/softsynth/pcspk.cpp -sound/softsynth/ym2612.cpp +audio/fmopl.cpp +audio/musicplugin.cpp +audio/null.h +audio/null.cpp +audio/mods/paula.cpp +audio/softsynth/adlib.cpp +audio/softsynth/appleiigs.cpp +audio/softsynth/sid.cpp +audio/softsynth/mt32.cpp +audio/softsynth/pcspk.cpp +audio/softsynth/ym2612.cpp backends/keymapper/remap-dialog.cpp backends/midi/windows.cpp diff --git a/po/ca_ES.po b/po/ca_ES.po index 0b654ac4b8..993aee9874 100644 --- a/po/ca_ES.po +++ b/po/ca_ES.po @@ -157,7 +157,7 @@ msgstr "" #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "" @@ -1776,51 +1776,51 @@ msgstr "Desa la partida:" msgid "Restore" msgstr "Restaura" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "Emulador OPL de MAME" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "Emulador OPL DOSBox" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "Sense música" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "Emulador d'àudio Amiga" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "Emulador d'AdLib" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "Emulador d'Apple II GS (NO IMPLEMENTAT)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "Emulador d'àudio C64" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "Iniciant l'Emulador de MT-32" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "Emulador de MT-32" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "Emulador Altaveu PC" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "Emulador d'IBM PCjr" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "Emulador de FM Towns" diff --git a/po/cs_CZ.po b/po/cs_CZ.po index 69be496a79..865e9647ff 100644 --- a/po/cs_CZ.po +++ b/po/cs_CZ.po @@ -159,7 +159,7 @@ msgstr "Jazyk hry. Toto z Va #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "" @@ -1746,51 +1746,51 @@ msgstr "Obnovit hru" msgid "Restore" msgstr "Obnovit" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "MAME OPL Emulátor" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "DOSBox OPL Emulátor" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "Bez hudby" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "Emulátor zvuku Amiga" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "AdLib Emulátor" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "Apple II GS Emulátor (NENÍ ZAVEDEN)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "Emulátor zvuku C64" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "Spou¹tím MT-32 Emulátor" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "MT-32 Emulátor" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "PC Speaker Emulátor" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "IBM PCjr Emulátor" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "FM Towns Emulátor" diff --git a/po/da_DA.po b/po/da_DA.po index d1b8bb7a7f..429d4493cb 100644 --- a/po/da_DA.po +++ b/po/da_DA.po @@ -157,7 +157,7 @@ msgstr "" #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "" @@ -1743,51 +1743,51 @@ msgstr "Gendan spil:" msgid "Restore" msgstr "Gendan" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "MAME OPL emulator" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "DOSBox OPL emulator" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "Ingen musik" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "Amiga lyd emulator" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "AdLib emulator" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "Apple II GS emulator (IKKE IMPLEMENTERET)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "C64 lyd emulator" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "Initialisere MT-32 emulator" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "MT-32 emulator" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "PC Speaker emulator" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "IBM PCjr emulator" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "FM Towns emulator" diff --git a/po/de_DE.po b/po/de_DE.po index 79af360652..a3a7cdfd23 100644 --- a/po/de_DE.po +++ b/po/de_DE.po @@ -159,7 +159,7 @@ msgstr "" #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "" @@ -1757,51 +1757,51 @@ msgstr "Spiel laden:" msgid "Restore" msgstr "Laden" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "MAME-OPL-Emulator" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "DOSBox-OPL-Emulator" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "Keine Musik" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "Amiga-Audio-Emulator" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "AdLib-Emulator" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "Apple-II-GS-Emulator (NICHT INTEGRIERT)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "C64-Audio-Emulator" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "MT-32-Emulator wird gestartet..." -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "MT-32-Emulation" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "PC-Lautsprecher-Emulator" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "IBM-PCjr-Emulator" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "FM-Towns-Emulator" diff --git a/po/es_ES.po b/po/es_ES.po index 6e954d54fd..8b3c57fdb0 100644 --- a/po/es_ES.po +++ b/po/es_ES.po @@ -157,7 +157,7 @@ msgstr "" #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "" @@ -1770,51 +1770,51 @@ msgstr "Cargar partida:" msgid "Restore" msgstr "Cargar" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "Emulador OPL de MAME" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "Emulador OPL de DOSBox" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "Sin música" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "Emulador de Amiga Audio" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "Emulador de AdLib" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "Emulador de Apple II GS (NO IMPLEMENTADO)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "Emulador de C64 Audio" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "Iniciando emulador de MT-32" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "Emulador de MT-32" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "Emulador del altavoz de PC" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "Emulador de IBM PCjr" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "Emulador de FM Towns" diff --git a/po/fr_FR.po b/po/fr_FR.po index f2785b3e63..cdadf119cd 100644 --- a/po/fr_FR.po +++ b/po/fr_FR.po @@ -158,7 +158,7 @@ msgstr "" #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "" @@ -1760,51 +1760,51 @@ msgstr "Charger le jeu:" msgid "Restore" msgstr "Charger" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "Émulateur MAME OPL" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "Émulateur DOSBox OPL" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "Pas de musique" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "Émulateur Amiga Audio" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "Émulateur AdLib" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "Émulateur Apple II GS (PAS IMPLÉMENTÉ)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "Émulateur C64 Audio" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "Initialisation de l'Émulateur MT-32" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "Émulateur MT-32" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "Émulateur Haut Parleur PC" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "Émulateur IBM PCjr" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "Émulateur FM Towns" diff --git a/po/hu_HU.po b/po/hu_HU.po index fc282f9dbb..80b40da1f4 100644 --- a/po/hu_HU.po +++ b/po/hu_HU.po @@ -159,7 +159,7 @@ msgstr "" #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "" @@ -1745,51 +1745,51 @@ msgstr "J msgid "Restore" msgstr "Visszaállítás" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "MAME OPL emulátor" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "DOSBox OPL emulátor" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "Nincs zene" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "Amiga Audió Emulátor" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "AdLib Emulátor" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "Apple II GS Emulátor (NEM TÁMOGATOTT)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "C64 Audio Emulátor" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "MT-32 Emulátor inicializálás" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "MT-32 Emulátor" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "PC Speaker Emulátor" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "IBM PCjr Emulátor" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "FM Towns Emulátor" diff --git a/po/it_IT.po b/po/it_IT.po index 2f2da61979..705a6b4fb4 100644 --- a/po/it_IT.po +++ b/po/it_IT.po @@ -156,7 +156,7 @@ msgstr "" #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "" @@ -1771,51 +1771,51 @@ msgstr "Ripristina gioco:" msgid "Restore" msgstr "Ripristina" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "Emulatore OPL MAME" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "Emulatore OPL DOSBox" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "Nessuna musica" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "Emulatore audio Amiga" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "Emulatore AdLib" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "Emulatore Apple II GS (NON IMPLEMENTATO)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "Emulatore audio C64" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "Avvio in corso dell'emulatore MT-32" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "Emulatore MT-32" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "Emulatore PC Speaker" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "Emulatore IBM PCjr" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "Emulatore FM Towns" diff --git a/po/nb_NO.po b/po/nb_NO.po index 7b9c61744a..54bc891595 100644 --- a/po/nb_NO.po +++ b/po/nb_NO.po @@ -161,7 +161,7 @@ msgstr "" #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "" @@ -1747,51 +1747,51 @@ msgstr "Gjennopprett spill:" msgid "Restore" msgstr "Gjenopprett" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "MAME OPL emulator" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "DOSBox OPL emulator" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "Ingen musikk" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "Amiga Lydemulator" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "AdLib Emulator" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "Apple II GS Emulator (IKKE IMPLEMENTERT)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "C64 Lydemulator" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "Initialiserer MT-32-Emulator" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "MT-32 Emulator" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "PC Speaker Emulator" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "IBM PCjr Emulator" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "FM Towns Emulator" diff --git a/po/nn_NO.po b/po/nn_NO.po index f690618228..7b2c54eed8 100644 --- a/po/nn_NO.po +++ b/po/nn_NO.po @@ -161,7 +161,7 @@ msgstr "" #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "" @@ -1741,51 +1741,51 @@ msgstr "Gjenopprett spel:" msgid "Restore" msgstr "Gjenopprett" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "MAME OPL emulator" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "DOSBox OPL emulator" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "Ingen musikk" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "Amiga Lydemulator" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "AdLib Emulator" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "Apple II GS Emulator (IKKJE IMPLEMENTERT)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "C64 Lydemulator" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "Initialiserar MT-32-emulator" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "MT-32 Emulator" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "PC Speaker Emulator" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "IBM PCjr Emulator" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "FM Towns Emulator" diff --git a/po/pl_PL.po b/po/pl_PL.po index c9f8b2caf0..6d27cc8c65 100644 --- a/po/pl_PL.po +++ b/po/pl_PL.po @@ -159,7 +159,7 @@ msgstr "J #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "" @@ -1747,51 +1747,51 @@ msgstr "Wzn msgid "Restore" msgstr "Wznów" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "Emulator OPL MAME" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "Emulator OPL DOSBox" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "Brak muzyki" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "Emulator d¼wiêku Amigi" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "Emulator AdLib" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "Emulator Apple II GS (NIE ZAIMPLEMENTOWANY)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "Emulator d¼wiêku C64" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "Inicjalizacja emulatora MT-32" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "Emulator MT-32" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "Emulator brzêczyka" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "Emulator IBM PCjr" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "Emulator FM Towns" diff --git a/po/pt_BR.po b/po/pt_BR.po index 193967446f..3c477faa88 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -159,7 +159,7 @@ msgstr "Idioma do jogo. Isto n #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "" @@ -1754,51 +1754,51 @@ msgstr "Restaurar jogo:" msgid "Restore" msgstr "Restaurar" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "Emulador MAME OPL" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "Emulador DOSBox OPL" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "Sem música" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "Emulador Som Amiga" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "Emulador AdLib" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "Emulador Apple II GS (NÃO IMPLEMENTADO)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "Emulador Som C64" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "Inicializando Emulador MT-32" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "Emulador MT-32" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "Emulador PC Speaker" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "Emulador IBM PCjr" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "Emulador FM Towns" diff --git a/po/ru_RU.po b/po/ru_RU.po index 0148944466..01f73849d4 100644 --- a/po/ru_RU.po +++ b/po/ru_RU.po @@ -159,7 +159,7 @@ msgstr "" #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "<ßÞ ãÜÞÛçÐÝØî>" @@ -1773,51 +1773,51 @@ msgstr " msgid "Restore" msgstr "²ÞááâÒÝÞÒØâì" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "ÍÜãÛïâÞà MAME OPL" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "ÍÜãÛïâÞà DOSBox OPL" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "±Õ× Üã×ëÚØ" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "ÍÜãÛïâÞà ×ÒãÚÐ Amiga" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "ÍÜãÛïâÞà AdLib" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "ÍÜãÛïâÞà Apple II GS (ÞâáãâáâÒãÕâ)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "ÍÜãÛïâÞà ×ÒãÚÐ C64" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "½ÐáâàÐØÒÐî íÜãÛïâÞà MT-32" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "ÍÜãÛïâÞà MT-32" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "ÍÜãÛïâÞà PC áßØÚÕàÐ" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "ÍÜãÛïâÞà IBM PCjr" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "ÍÜãÛïâÞà FM Towns" diff --git a/po/scummvm.pot b/po/scummvm.pot index 523d48ee1e..e1be51f70b 100644 --- a/po/scummvm.pot +++ b/po/scummvm.pot @@ -153,7 +153,7 @@ msgstr "" #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "" @@ -1721,51 +1721,51 @@ msgstr "" msgid "Restore" msgstr "" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "" diff --git a/po/uk_UA.po b/po/uk_UA.po index 56d1880e1f..50e7871355 100644 --- a/po/uk_UA.po +++ b/po/uk_UA.po @@ -159,7 +159,7 @@ msgstr "" #: gui/launcher.cpp:191 gui/launcher.cpp:205 gui/options.cpp:80 #: gui/options.cpp:646 gui/options.cpp:656 gui/options.cpp:1087 -#: sound/null.cpp:42 +#: audio/null.cpp:42 msgid "" msgstr "<×Ð ãÜÞÒçÐÝÝïÜ>" @@ -1769,51 +1769,51 @@ msgstr " msgid "Restore" msgstr "²öÔÝÞÒØâØ" -#: sound/fmopl.cpp:51 +#: audio/fmopl.cpp:51 msgid "MAME OPL emulator" msgstr "µÜãÛïâÞà MAME OPL:" -#: sound/fmopl.cpp:53 +#: audio/fmopl.cpp:53 msgid "DOSBox OPL emulator" msgstr "µÜãÛïâÞà DOSBox OPL" -#: sound/null.h:45 +#: audio/null.h:45 msgid "No music" msgstr "±Õ× Üã×ØÚØ" -#: sound/mods/paula.cpp:192 +#: audio/mods/paula.cpp:192 msgid "Amiga Audio Emulator" msgstr "°ÜöÓÐ °ãÔöÞ µÜãÛïâÞà" -#: sound/softsynth/adlib.cpp:1590 +#: audio/softsynth/adlib.cpp:1590 msgid "AdLib Emulator" msgstr "µÜãÛïâÞà AdLib" -#: sound/softsynth/appleiigs.cpp:36 +#: audio/softsynth/appleiigs.cpp:36 msgid "Apple II GS Emulator (NOT IMPLEMENTED)" msgstr "Apple II GS µÜãÛïâÞà (½µ Àµ°»¦·¾²°½¾)" -#: sound/softsynth/sid.cpp:1434 +#: audio/softsynth/sid.cpp:1434 msgid "C64 Audio Emulator" msgstr "C64 °ãÔöÞ µÜãÛïâÞà" -#: sound/softsynth/mt32.cpp:327 +#: audio/softsynth/mt32.cpp:327 msgid "Initialising MT-32 Emulator" msgstr "½ÐáâàÞîî ÕÜãÛïâÞà MT-32" -#: sound/softsynth/mt32.cpp:541 +#: audio/softsynth/mt32.cpp:541 msgid "MT-32 Emulator" msgstr "µÜãÛïâÞà MT-32" -#: sound/softsynth/pcspk.cpp:142 +#: audio/softsynth/pcspk.cpp:142 msgid "PC Speaker Emulator" msgstr "µÜãÛïâÞà PC áßöÚÕàÐ" -#: sound/softsynth/pcspk.cpp:161 +#: audio/softsynth/pcspk.cpp:161 msgid "IBM PCjr Emulator" msgstr "µÜãÛïâÞà IBM PCjr" -#: sound/softsynth/ym2612.cpp:762 +#: audio/softsynth/ym2612.cpp:762 msgid "FM Towns Emulator" msgstr "µÜãÛïâÞà FM Towns" diff --git a/sound/audiostream.cpp b/sound/audiostream.cpp deleted file mode 100644 index b3efb2cefe..0000000000 --- a/sound/audiostream.cpp +++ /dev/null @@ -1,407 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "common/debug.h" -#include "common/endian.h" -#include "common/file.h" -#include "common/queue.h" -#include "common/util.h" - -#include "sound/audiostream.h" -#include "sound/decoders/flac.h" -#include "sound/mixer.h" -#include "sound/decoders/mp3.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/vorbis.h" - - -namespace Audio { - -struct StreamFileFormat { - /** Decodername */ - const char *decoderName; - const char *fileExtension; - /** - * Pointer to a function which tries to open a file of type StreamFormat. - * Return NULL in case of an error (invalid/nonexisting file). - */ - SeekableAudioStream *(*openStreamFile)(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse); -}; - -static const StreamFileFormat STREAM_FILEFORMATS[] = { - /* decoderName, fileExt, openStreamFuntion */ -#ifdef USE_FLAC - { "FLAC", ".flac", makeFLACStream }, - { "FLAC", ".fla", makeFLACStream }, -#endif -#ifdef USE_VORBIS - { "Ogg Vorbis", ".ogg", makeVorbisStream }, -#endif -#ifdef USE_MAD - { "MPEG Layer 3", ".mp3", makeMP3Stream }, -#endif - - { NULL, NULL, NULL } // Terminator -}; - -SeekableAudioStream *SeekableAudioStream::openStreamFile(const Common::String &basename) { - SeekableAudioStream *stream = NULL; - Common::File *fileHandle = new Common::File(); - - for (int i = 0; i < ARRAYSIZE(STREAM_FILEFORMATS)-1 && stream == NULL; ++i) { - Common::String filename = basename + STREAM_FILEFORMATS[i].fileExtension; - fileHandle->open(filename); - if (fileHandle->isOpen()) { - // Create the stream object - stream = STREAM_FILEFORMATS[i].openStreamFile(fileHandle, DisposeAfterUse::YES); - fileHandle = 0; - break; - } - } - - delete fileHandle; - - if (stream == NULL) - debug(1, "SeekableAudioStream::openStreamFile: Could not open compressed AudioFile %s", basename.c_str()); - - return stream; -} - -#pragma mark - -#pragma mark --- LoopingAudioStream --- -#pragma mark - - -LoopingAudioStream::LoopingAudioStream(RewindableAudioStream *stream, uint loops, DisposeAfterUse::Flag disposeAfterUse) - : _parent(stream), _disposeAfterUse(disposeAfterUse), _loops(loops), _completeIterations(0) { - assert(stream); - - if (!stream->rewind()) { - // TODO: Properly indicate error - _loops = _completeIterations = 1; - } -} - -LoopingAudioStream::~LoopingAudioStream() { - if (_disposeAfterUse == DisposeAfterUse::YES) - delete _parent; -} - -int LoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) { - if ((_loops && _completeIterations == _loops) || !numSamples) - return 0; - - int samplesRead = _parent->readBuffer(buffer, numSamples); - - if (_parent->endOfStream()) { - ++_completeIterations; - if (_completeIterations == _loops) - return samplesRead; - - const int remainingSamples = numSamples - samplesRead; - - if (!_parent->rewind()) { - // TODO: Properly indicate error - _loops = _completeIterations = 1; - return samplesRead; - } - - return samplesRead + readBuffer(buffer + samplesRead, remainingSamples); - } - - return samplesRead; -} - -bool LoopingAudioStream::endOfData() const { - return (_loops != 0 && (_completeIterations == _loops)); -} - -AudioStream *makeLoopingAudioStream(RewindableAudioStream *stream, uint loops) { - if (loops != 1) - return new LoopingAudioStream(stream, loops); - else - return stream; -} - -AudioStream *makeLoopingAudioStream(SeekableAudioStream *stream, Timestamp start, Timestamp end, uint loops) { - if (!start.totalNumberOfFrames() && (!end.totalNumberOfFrames() || end == stream->getLength())) { - return makeLoopingAudioStream(stream, loops); - } else { - if (!end.totalNumberOfFrames()) - end = stream->getLength(); - - if (start >= end) { - warning("makeLoopingAudioStream: start (%d) >= end (%d)", start.msecs(), end.msecs()); - delete stream; - return 0; - } - - return makeLoopingAudioStream(new SubSeekableAudioStream(stream, start, end), loops); - } -} - -#pragma mark - -#pragma mark --- SubLoopingAudioStream --- -#pragma mark - - -SubLoopingAudioStream::SubLoopingAudioStream(SeekableAudioStream *stream, - uint loops, - const Timestamp loopStart, - const Timestamp loopEnd, - DisposeAfterUse::Flag disposeAfterUse) - : _parent(stream), _disposeAfterUse(disposeAfterUse), _loops(loops), - _pos(0, getRate() * (isStereo() ? 2 : 1)), - _loopStart(convertTimeToStreamPos(loopStart, getRate(), isStereo())), - _loopEnd(convertTimeToStreamPos(loopEnd, getRate(), isStereo())), - _done(false) { - assert(loopStart < loopEnd); - - if (!_parent->rewind()) - _done = true; -} - -SubLoopingAudioStream::~SubLoopingAudioStream() { - if (_disposeAfterUse == DisposeAfterUse::YES) - delete _parent; -} - -int SubLoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) { - if (_done) - return 0; - - int framesLeft = MIN(_loopEnd.frameDiff(_pos), numSamples); - int framesRead = _parent->readBuffer(buffer, framesLeft); - _pos = _pos.addFrames(framesRead); - - if (framesRead < framesLeft && _parent->endOfData()) { - // TODO: Proper error indication. - _done = true; - return framesRead; - } else if (_pos == _loopEnd) { - if (_loops != 0) { - --_loops; - if (!_loops) { - _done = true; - return framesRead; - } - } - - if (!_parent->seek(_loopStart)) { - // TODO: Proper error indication. - _done = true; - return framesRead; - } - - _pos = _loopStart; - framesLeft = numSamples - framesLeft; - return framesRead + readBuffer(buffer + framesRead, framesLeft); - } else { - return framesRead; - } -} - -#pragma mark - -#pragma mark --- SubSeekableAudioStream --- -#pragma mark - - -SubSeekableAudioStream::SubSeekableAudioStream(SeekableAudioStream *parent, const Timestamp start, const Timestamp end, DisposeAfterUse::Flag disposeAfterUse) - : _parent(parent), _disposeAfterUse(disposeAfterUse), - _start(convertTimeToStreamPos(start, getRate(), isStereo())), - _pos(0, getRate() * (isStereo() ? 2 : 1)), - _length(convertTimeToStreamPos(end - start, getRate(), isStereo())) { - - assert(_length.totalNumberOfFrames() % (isStereo() ? 2 : 1) == 0); - _parent->seek(_start); -} - -SubSeekableAudioStream::~SubSeekableAudioStream() { - if (_disposeAfterUse) - delete _parent; -} - -int SubSeekableAudioStream::readBuffer(int16 *buffer, const int numSamples) { - int framesLeft = MIN(_length.frameDiff(_pos), numSamples); - int framesRead = _parent->readBuffer(buffer, framesLeft); - _pos = _pos.addFrames(framesRead); - return framesRead; -} - -bool SubSeekableAudioStream::seek(const Timestamp &where) { - _pos = convertTimeToStreamPos(where, getRate(), isStereo()); - if (_pos > _length) { - _pos = _length; - return false; - } - - if (_parent->seek(_pos + _start)) { - return true; - } else { - _pos = _length; - return false; - } -} - -#pragma mark - -#pragma mark --- Queueing audio stream --- -#pragma mark - - - -void QueuingAudioStream::queueBuffer(byte *data, uint32 size, DisposeAfterUse::Flag disposeAfterUse, byte flags) { - AudioStream *stream = makeRawStream(data, size, getRate(), flags, disposeAfterUse); - queueAudioStream(stream, DisposeAfterUse::YES); -} - - -class QueuingAudioStreamImpl : public QueuingAudioStream { -private: - /** - * We queue a number of (pointers to) audio stream objects. - * In addition, we need to remember for each stream whether - * to dispose it after all data has been read from it. - * Hence, we don't store pointers to stream objects directly, - * but rather StreamHolder structs. - */ - struct StreamHolder { - AudioStream *_stream; - DisposeAfterUse::Flag _disposeAfterUse; - StreamHolder(AudioStream *stream, DisposeAfterUse::Flag disposeAfterUse) - : _stream(stream), - _disposeAfterUse(disposeAfterUse) {} - }; - - /** - * The sampling rate of this audio stream. - */ - const int _rate; - - /** - * Whether this audio stream is mono (=false) or stereo (=true). - */ - const int _stereo; - - /** - * This flag is set by the finish() method only. See there for more details. - */ - bool _finished; - - /** - * A mutex to avoid access problems (causing e.g. corruption of - * the linked list) in thread aware environments. - */ - Common::Mutex _mutex; - - /** - * The queue of audio streams. - */ - Common::Queue _queue; - -public: - QueuingAudioStreamImpl(int rate, bool stereo) - : _rate(rate), _stereo(stereo), _finished(false) {} - ~QueuingAudioStreamImpl(); - - // Implement the AudioStream API - virtual int readBuffer(int16 *buffer, const int numSamples); - virtual bool isStereo() const { return _stereo; } - virtual int getRate() const { return _rate; } - virtual bool endOfData() const { - //Common::StackLock lock(_mutex); - return _queue.empty(); - } - virtual bool endOfStream() const { return _finished && _queue.empty(); } - - // Implement the QueuingAudioStream API - virtual void queueAudioStream(AudioStream *stream, DisposeAfterUse::Flag disposeAfterUse); - virtual void finish() { _finished = true; } - - uint32 numQueuedStreams() const { - //Common::StackLock lock(_mutex); - return _queue.size(); - } -}; - -QueuingAudioStreamImpl::~QueuingAudioStreamImpl() { - while (!_queue.empty()) { - StreamHolder tmp = _queue.pop(); - if (tmp._disposeAfterUse == DisposeAfterUse::YES) - delete tmp._stream; - } -} - -void QueuingAudioStreamImpl::queueAudioStream(AudioStream *stream, DisposeAfterUse::Flag disposeAfterUse) { - assert(!_finished); - if ((stream->getRate() != getRate()) || (stream->isStereo() != isStereo())) - error("QueuingAudioStreamImpl::queueAudioStream: stream has mismatched parameters"); - - Common::StackLock lock(_mutex); - _queue.push(StreamHolder(stream, disposeAfterUse)); -} - -int QueuingAudioStreamImpl::readBuffer(int16 *buffer, const int numSamples) { - Common::StackLock lock(_mutex); - int samplesDecoded = 0; - - while (samplesDecoded < numSamples && !_queue.empty()) { - AudioStream *stream = _queue.front()._stream; - samplesDecoded += stream->readBuffer(buffer + samplesDecoded, numSamples - samplesDecoded); - - if (stream->endOfData()) { - StreamHolder tmp = _queue.pop(); - if (tmp._disposeAfterUse == DisposeAfterUse::YES) - delete stream; - } - } - - return samplesDecoded; -} - -QueuingAudioStream *makeQueuingAudioStream(int rate, bool stereo) { - return new QueuingAudioStreamImpl(rate, stereo); -} - -Timestamp convertTimeToStreamPos(const Timestamp &where, int rate, bool isStereo) { - Timestamp result(where.convertToFramerate(rate * (isStereo ? 2 : 1))); - - // When the Stream is a stereo stream, we have to assure - // that the sample position is an even number. - if (isStereo && (result.totalNumberOfFrames() & 1)) - result = result.addFrames(-1); // We cut off one sample here. - - // Since Timestamp allows sub-frame-precision it might lead to odd behaviors - // when we would just return result. - // - // An example is when converting the timestamp 500ms to a 11025 Hz based - // stream. It would have an internal frame counter of 5512.5. Now when - // doing calculations at frame precision, this might lead to unexpected - // results: The frame difference between a timestamp 1000ms and the above - // mentioned timestamp (both with 11025 as framerate) would be 5512, - // instead of 5513, which is what a frame-precision based code would expect. - // - // By creating a new Timestamp with the given parameters, we create a - // Timestamp with frame-precision, which just drops a sub-frame-precision - // information (i.e. rounds down). - return Timestamp(result.secs(), result.numberOfFrames(), result.framerate()); -} - -} // End of namespace Audio diff --git a/sound/audiostream.h b/sound/audiostream.h deleted file mode 100644 index b2c012841d..0000000000 --- a/sound/audiostream.h +++ /dev/null @@ -1,371 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef SOUND_AUDIOSTREAM_H -#define SOUND_AUDIOSTREAM_H - -#include "common/util.h" -#include "common/scummsys.h" -#include "common/types.h" - -#include "sound/timestamp.h" - -namespace Audio { - -class SeekableAudioStream; - -/** - * Generic audio input stream. Subclasses of this are used to feed arbitrary - * sampled audio data into ScummVM's audio mixer. - */ -class AudioStream { -public: - virtual ~AudioStream() {} - - /** - * Fill the given buffer with up to numSamples samples. Returns the actual - * number of samples read, or -1 if a critical error occurred (note: you - * *must* check if this value is less than what you requested, this can - * happen when the stream is fully used up). - * - * Data has to be in native endianess, 16 bit per sample, signed. For stereo - * stream, buffer will be filled with interleaved left and right channel - * samples, starting with a left sample. Furthermore, the samples in the - * left and right are summed up. So if you request 4 samples from a stereo - * stream, you will get a total of two left channel and two right channel - * samples. - */ - virtual int readBuffer(int16 *buffer, const int numSamples) = 0; - - /** Is this a stereo stream? */ - virtual bool isStereo() const = 0; - - /** Sample rate of the stream. */ - virtual int getRate() const = 0; - - /** - * End of data reached? If this returns true, it means that at this - * time there is no data available in the stream. However there may be - * more data in the future. - * This is used by e.g. a rate converter to decide whether to keep on - * converting data or stop. - */ - virtual bool endOfData() const = 0; - - /** - * End of stream reached? If this returns true, it means that all data - * in this stream is used up and no additional data will appear in it - * in the future. - * This is used by the mixer to decide whether a given stream shall be - * removed from the list of active streams (and thus be destroyed). - * By default this maps to endOfData() - */ - virtual bool endOfStream() const { return endOfData(); } -}; - -/** - * A rewindable audio stream. This allows for reseting the AudioStream - * to its initial state. Note that rewinding itself is not required to - * be working when the stream is being played by Mixer! - */ -class RewindableAudioStream : public AudioStream { -public: - /** - * Rewinds the stream to its start. - * - * @return true on success, false otherwise. - */ - virtual bool rewind() = 0; -}; - -/** - * A looping audio stream. This object does nothing besides using - * a RewindableAudioStream to play a stream in a loop. - */ -class LoopingAudioStream : public AudioStream { -public: - /** - * Creates a looping audio stream object. - * - * Note that on creation of the LoopingAudioStream object - * the underlying stream will be rewound. - * - * @see makeLoopingAudioStream - * - * @param stream Stream to loop - * @param loops How often to loop (0 = infinite) - * @param disposeAfterUse Destroy the stream after the LoopingAudioStream has finished playback. - */ - LoopingAudioStream(RewindableAudioStream *stream, uint loops, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); - ~LoopingAudioStream(); - - int readBuffer(int16 *buffer, const int numSamples); - bool endOfData() const; - - bool isStereo() const { return _parent->isStereo(); } - int getRate() const { return _parent->getRate(); } - - /** - * Returns number of loops the stream has played. - * - * @param numLoops number of loops to play, 0 - infinite - */ - uint getCompleteIterations() const { return _completeIterations; } -private: - RewindableAudioStream *_parent; - DisposeAfterUse::Flag _disposeAfterUse; - - uint _loops; - uint _completeIterations; -}; - -/** - * Wrapper functionality to efficiently create a stream, which might be looped. - * - * Note that this function does not return a LoopingAudioStream, because it does - * not create one when the loop count is "1". This allows to keep the runtime - * overhead down, when the code does not require any functionality only offered - * by LoopingAudioStream. - * - * @param stream Stream to loop (will be automatically destroyed, when the looping is done) - * @param loops How often to loop (0 = infinite) - * @return A new AudioStream, which offers the desired functionality. - */ -AudioStream *makeLoopingAudioStream(RewindableAudioStream *stream, uint loops); - -/** - * A seekable audio stream. Subclasses of this class implement an - * interface for seeking. The seeking itself is not required to be - * working while the stream is being played by Mixer! - */ -class SeekableAudioStream : public RewindableAudioStream { -public: - /** - * Tries to load a file by trying all available formats. - * In case of an error, the file handle will be closed, but deleting - * it is still the responsibility of the caller. - * - * @param basename a filename without an extension - * @return an SeekableAudioStream ready to use in case of success; - * NULL in case of an error (e.g. invalid/nonexisting file) - */ - static SeekableAudioStream *openStreamFile(const Common::String &basename); - - /** - * Seeks to a given offset in the stream. - * - * @param where offset in milliseconds - * @return true on success, false on failure. - */ - bool seek(uint32 where) { - return seek(Timestamp(where, getRate())); - } - - /** - * Seeks to a given offset in the stream. - * - * @param where offset as timestamp - * @return true on success, false on failure. - */ - virtual bool seek(const Timestamp &where) = 0; - - /** - * Returns the length of the stream. - * - * @return length as Timestamp. - */ - virtual Timestamp getLength() const = 0; - - virtual bool rewind() { return seek(0); } -}; - -/** - * Wrapper functionality to efficiently create a stream, which might be looped - * in a certain interval. - * - * This automatically starts the stream at time "start"! - * - * Note that this function does not return a LoopingAudioStream, because it does - * not create one when the loop count is "1". This allows to keep the runtime - * overhead down, when the code does not require any functionality only offered - * by LoopingAudioStream. - * - * @param stream Stream to loop (will be automatically destroyed, when the looping is done) - * @param start Starttime of the stream interval to be looped - * @param end End of the stream interval to be looped (a zero time, means till end) - * @param loops How often to loop (0 = infinite) - * @return A new AudioStream, which offers the desired functionality. - */ -AudioStream *makeLoopingAudioStream(SeekableAudioStream *stream, Timestamp start, Timestamp end, uint loops); - -/** - * A looping audio stream, which features looping of a nested part of the - * stream. - * - * NOTE: - * Currently this implementation stops after the nested loop finished - * playback. - * - * IMPORTANT: - * This might be merged with SubSeekableAudioStream for playback purposes. - * (After extending it to accept a start time). - */ -class SubLoopingAudioStream : public AudioStream { -public: - /** - * Constructor for a SubLoopingAudioStream. - * - * Note that on creation of the SubLoopingAudioStream object - * the underlying stream will be rewound. - * - * @param stream Stream to loop - * @param loops How often the stream should be looped (0 means infinite) - * @param loopStart Start of the loop (this must be smaller than loopEnd) - * @param loopEnd End of the loop (thus must be greater than loopStart) - * @param disposeAfterUse Whether the stream should be disposed, when the - * SubLoopingAudioStream is destroyed. - */ - SubLoopingAudioStream(SeekableAudioStream *stream, uint loops, - const Timestamp loopStart, - const Timestamp loopEnd, - DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); - ~SubLoopingAudioStream(); - - int readBuffer(int16 *buffer, const int numSamples); - bool endOfData() const { return _done; } - - bool isStereo() const { return _parent->isStereo(); } - int getRate() const { return _parent->getRate(); } -private: - SeekableAudioStream *_parent; - DisposeAfterUse::Flag _disposeAfterUse; - - uint _loops; - Timestamp _pos; - Timestamp _loopStart, _loopEnd; - - bool _done; -}; - - -/** - * A SubSeekableAudioStream provides access to a SeekableAudioStream - * just in the range [start, end). - * The same caveats apply to SubSeekableAudioStream as do to SeekableAudioStream. - * - * Manipulating the parent stream directly /will/ mess up a substream. - */ -class SubSeekableAudioStream : public SeekableAudioStream { -public: - /** - * Creates a new SubSeekableAudioStream. - * - * @param parent parent stream object. - * @param start Start time. - * @param end End time. - * @param disposeAfterUse Whether the parent stream object should be destroyed on destruction of the SubSeekableAudioStream. - */ - SubSeekableAudioStream(SeekableAudioStream *parent, const Timestamp start, const Timestamp end, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); - ~SubSeekableAudioStream(); - - int readBuffer(int16 *buffer, const int numSamples); - - bool isStereo() const { return _parent->isStereo(); } - - int getRate() const { return _parent->getRate(); } - - bool endOfData() const { return (_pos >= _length) || _parent->endOfStream(); } - - bool seek(const Timestamp &where); - - Timestamp getLength() const { return _length; } -private: - SeekableAudioStream *_parent; - DisposeAfterUse::Flag _disposeAfterUse; - - const Timestamp _start; - const Timestamp _length; - Timestamp _pos; -}; - -class QueuingAudioStream : public Audio::AudioStream { -public: - - /** - * Queue an audio stream for playback. This stream plays all queued - * streams, in the order they were queued. If disposeAfterUse is set to - * DisposeAfterUse::YES, then the queued stream is deleted after all data - * contained in it has been played. - */ - virtual void queueAudioStream(Audio::AudioStream *audStream, - DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES) = 0; - - /** - * Queue a block of raw audio data for playback. This stream plays all - * queued block, in the order they were queued. If disposeAfterUse is set - * to DisposeAfterUse::YES, then the queued block is released using free() - * after all data contained in it has been played. - * - * @note Make sure to allocate the data block with malloc(), not with new[]. - * - * @param data pointer to the audio data block - * @param size length of the audio data block - * @param disposeAfterUse if equal to DisposeAfterUse::YES, the block is released using free() after use. - * @param flags a bit-ORed combination of RawFlags describing the audio data format - */ - void queueBuffer(byte *data, uint32 size, DisposeAfterUse::Flag disposeAfterUse, byte flags); - - /** - * Mark this stream as finished. That is, signal that no further data - * will be queued to it. Only after this has been done can this - * stream ever 'end'. - */ - virtual void finish() = 0; - - /** - * Return the number of streams still queued for playback (including - * the currently playing stream). - */ - virtual uint32 numQueuedStreams() const = 0; -}; - -/** - * Factory function for an QueuingAudioStream. - */ -QueuingAudioStream *makeQueuingAudioStream(int rate, bool stereo); - -/** - * Converts a point in time to a precise sample offset - * with the given parameters. - * - * @param where Point in time. - * @param rate Rate of the stream. - * @param isStereo Is the stream a stereo stream? - */ -Timestamp convertTimeToStreamPos(const Timestamp &where, int rate, bool isStereo); - -} // End of namespace Audio - -#endif diff --git a/sound/decoders/adpcm.cpp b/sound/decoders/adpcm.cpp deleted file mode 100644 index 4b5e6a5b84..0000000000 --- a/sound/decoders/adpcm.cpp +++ /dev/null @@ -1,851 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "common/endian.h" - -#include "sound/decoders/adpcm.h" -#include "sound/audiostream.h" - - -namespace Audio { - -class ADPCMStream : public RewindableAudioStream { -protected: - Common::SeekableReadStream *_stream; - const DisposeAfterUse::Flag _disposeAfterUse; - const int32 _startpos; - const int32 _endpos; - const int _channels; - const uint32 _blockAlign; - uint32 _blockPos[2]; - const int _rate; - - struct { - // OKI/IMA - struct { - int32 last; - int32 stepIndex; - } ima_ch[2]; - } _status; - - virtual void reset(); - int16 stepAdjust(byte); - -public: - ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign); - ~ADPCMStream(); - - virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos); } - virtual bool isStereo() const { return _channels == 2; } - virtual int getRate() const { return _rate; } - - virtual bool rewind(); -}; - -// Routines to convert 12 bit linear samples to the -// Dialogic or Oki ADPCM coding format aka VOX. -// See also -// -// IMA ADPCM support is based on -// -// -// In addition, also MS IMA ADPCM is supported. See -// . - -ADPCMStream::ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) - : _stream(stream), - _disposeAfterUse(disposeAfterUse), - _startpos(stream->pos()), - _endpos(_startpos + size), - _channels(channels), - _blockAlign(blockAlign), - _rate(rate) { - - reset(); -} - -ADPCMStream::~ADPCMStream() { - if (_disposeAfterUse == DisposeAfterUse::YES) - delete _stream; -} - -void ADPCMStream::reset() { - memset(&_status, 0, sizeof(_status)); - _blockPos[0] = _blockPos[1] = _blockAlign; // To make sure first header is read -} - -bool ADPCMStream::rewind() { - // TODO: Error checking. - reset(); - _stream->seek(_startpos); - return true; -} - - -#pragma mark - - - -class Oki_ADPCMStream : public ADPCMStream { -public: - Oki_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) - : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {} - - virtual int readBuffer(int16 *buffer, const int numSamples); - -protected: - int16 decodeOKI(byte); -}; - -int Oki_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { - int samples; - byte data; - - assert(numSamples % 2 == 0); - - for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { - data = _stream->readByte(); - buffer[samples] = decodeOKI((data >> 4) & 0x0f); - buffer[samples + 1] = decodeOKI(data & 0x0f); - } - return samples; -} - -static const int16 okiStepSize[49] = { - 16, 17, 19, 21, 23, 25, 28, 31, - 34, 37, 41, 45, 50, 55, 60, 66, - 73, 80, 88, 97, 107, 118, 130, 143, - 157, 173, 190, 209, 230, 253, 279, 307, - 337, 371, 408, 449, 494, 544, 598, 658, - 724, 796, 876, 963, 1060, 1166, 1282, 1411, - 1552 -}; - -// Decode Linear to ADPCM -int16 Oki_ADPCMStream::decodeOKI(byte code) { - int16 diff, E, samp; - - E = (2 * (code & 0x7) + 1) * okiStepSize[_status.ima_ch[0].stepIndex] / 8; - diff = (code & 0x08) ? -E : E; - samp = _status.ima_ch[0].last + diff; - // Clip the values to +/- 2^11 (supposed to be 12 bits) - samp = CLIP(samp, -2048, 2047); - - _status.ima_ch[0].last = samp; - _status.ima_ch[0].stepIndex += stepAdjust(code); - _status.ima_ch[0].stepIndex = CLIP(_status.ima_ch[0].stepIndex, 0, ARRAYSIZE(okiStepSize) - 1); - - // * 16 effectively converts 12-bit input to 16-bit output - return samp * 16; -} - - -#pragma mark - - - -class Ima_ADPCMStream : public ADPCMStream { -protected: - int16 decodeIMA(byte code, int channel = 0); // Default to using the left channel/using one channel - -public: - Ima_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) - : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { - memset(&_status, 0, sizeof(_status)); - } - - virtual int readBuffer(int16 *buffer, const int numSamples); -}; - -int Ima_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { - int samples; - byte data; - - assert(numSamples % 2 == 0); - - for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { - data = _stream->readByte(); - buffer[samples] = decodeIMA((data >> 4) & 0x0f); - buffer[samples + 1] = decodeIMA(data & 0x0f, _channels == 2 ? 1 : 0); - } - return samples; -} - -#pragma mark - - - -class Apple_ADPCMStream : public Ima_ADPCMStream { -protected: - // Apple QuickTime IMA ADPCM - int32 _streamPos[2]; - int16 _buffer[2][2]; - uint8 _chunkPos[2]; - - void reset() { - Ima_ADPCMStream::reset(); - _chunkPos[0] = 0; - _chunkPos[1] = 0; - _streamPos[0] = 0; - _streamPos[1] = _blockAlign; - } - -public: - Apple_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) - : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { - _chunkPos[0] = 0; - _chunkPos[1] = 0; - _streamPos[0] = 0; - _streamPos[1] = _blockAlign; - } - - virtual int readBuffer(int16 *buffer, const int numSamples); - -}; - -int Apple_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { - // Need to write at least one samples per channel - assert((numSamples % _channels) == 0); - - // Current sample positions - int samples[2] = { 0, 0}; - - // Number of samples per channel - int chanSamples = numSamples / _channels; - - for (int i = 0; i < _channels; i++) { - _stream->seek(_streamPos[i]); - - while ((samples[i] < chanSamples) && - // Last byte read and a new one needed - !((_stream->eos() || (_stream->pos() >= _endpos)) && (_chunkPos[i] == 0))) { - - if (_blockPos[i] == _blockAlign) { - // 2 byte header per block - uint16 temp = _stream->readUint16BE(); - - // First 9 bits are the upper bits of the predictor - _status.ima_ch[i].last = (int16) (temp & 0xFF80); - // Lower 7 bits are the step index - _status.ima_ch[i].stepIndex = temp & 0x007F; - - // Clip the step index - _status.ima_ch[i].stepIndex = CLIP(_status.ima_ch[i].stepIndex, 0, 88); - - _blockPos[i] = 2; - } - - if (_chunkPos[i] == 0) { - // Decode data - byte data = _stream->readByte(); - _buffer[i][0] = decodeIMA(data & 0x0F, i); - _buffer[i][1] = decodeIMA(data >> 4, i); - } - - // The original is interleaved block-wise, we want it sample-wise - buffer[_channels * samples[i] + i] = _buffer[i][_chunkPos[i]]; - - if (++_chunkPos[i] > 1) { - // We're about to decode the next byte, so advance the block position - _chunkPos[i] = 0; - _blockPos[i]++; - } - - samples[i]++; - - if (_channels == 2) - if (_blockPos[i] == _blockAlign) - // We're at the end of the block. - // Since the channels are interleaved, skip the next block - _stream->skip(MIN(_blockAlign, _endpos - _stream->pos())); - - _streamPos[i] = _stream->pos(); - } - } - - return samples[0] + samples[1]; -} - -#pragma mark - - - -class MSIma_ADPCMStream : public Ima_ADPCMStream { -public: - MSIma_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign, bool invertSamples = false) - : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign), _invertSamples(invertSamples) { - if (blockAlign == 0) - error("ADPCMStream(): blockAlign isn't specified for MS IMA ADPCM"); - } - - virtual int readBuffer(int16 *buffer, const int numSamples) { - if (_channels == 1) - return readBufferMSIMA1(buffer, numSamples); - else - return readBufferMSIMA2(buffer, numSamples); - } - - int readBufferMSIMA1(int16 *buffer, const int numSamples); - int readBufferMSIMA2(int16 *buffer, const int numSamples); - -private: - bool _invertSamples; // Some implementations invert the way samples are decoded -}; - -int MSIma_ADPCMStream::readBufferMSIMA1(int16 *buffer, const int numSamples) { - int samples = 0; - byte data; - - assert(numSamples % 2 == 0); - - while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { - if (_blockPos[0] == _blockAlign) { - // read block header - _status.ima_ch[0].last = _stream->readSint16LE(); - _status.ima_ch[0].stepIndex = _stream->readSint16LE(); - _blockPos[0] = 4; - } - - for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { - data = _stream->readByte(); - _blockPos[0]++; - buffer[samples] = decodeIMA(_invertSamples ? (data >> 4) & 0x0f : data & 0x0f); - buffer[samples + 1] = decodeIMA(_invertSamples ? data & 0x0f : (data >> 4) & 0x0f); - } - } - return samples; -} - - -// Microsoft as usual tries to implement it differently. This method -// is used for stereo data. -int MSIma_ADPCMStream::readBufferMSIMA2(int16 *buffer, const int numSamples) { - int samples; - uint32 data; - int nibble; - byte k; - - // TODO: Currently this implementation only supports - // reading a multiple of 16 samples at once. We might - // consider changing that so it could read an arbitrary - // sample pair count. - assert(numSamples % 16 == 0); - - for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos;) { - for (int channel = 0; channel < 2; channel++) { - data = _stream->readUint32LE(); - - for (nibble = 0; nibble < 8; nibble++) { - k = ((data & 0xf0000000) >> 28); - buffer[samples + channel + nibble * 2] = decodeIMA(k); - data <<= 4; - } - } - samples += 16; - } - return samples; -} - - -#pragma mark - - - -static const int MSADPCMAdaptCoeff1[] = { - 256, 512, 0, 192, 240, 460, 392 -}; - -static const int MSADPCMAdaptCoeff2[] = { - 0, -256, 0, 64, 0, -208, -232 -}; - -static const int MSADPCMAdaptationTable[] = { - 230, 230, 230, 230, 307, 409, 512, 614, - 768, 614, 512, 409, 307, 230, 230, 230 -}; - - -class MS_ADPCMStream : public ADPCMStream { -protected: - struct ADPCMChannelStatus { - byte predictor; - int16 delta; - int16 coeff1; - int16 coeff2; - int16 sample1; - int16 sample2; - }; - - struct { - // MS ADPCM - ADPCMChannelStatus ch[2]; - } _status; - - void reset() { - ADPCMStream::reset(); - memset(&_status, 0, sizeof(_status)); - } - -public: - MS_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) - : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { - if (blockAlign == 0) - error("MS_ADPCMStream(): blockAlign isn't specified for MS ADPCM"); - memset(&_status, 0, sizeof(_status)); - } - - virtual int readBuffer(int16 *buffer, const int numSamples); - -protected: - int16 decodeMS(ADPCMChannelStatus *c, byte); -}; - -int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) { - int32 predictor; - - predictor = (((c->sample1) * (c->coeff1)) + ((c->sample2) * (c->coeff2))) / 256; - predictor += (signed)((code & 0x08) ? (code - 0x10) : (code)) * c->delta; - - predictor = CLIP(predictor, -32768, 32767); - - c->sample2 = c->sample1; - c->sample1 = predictor; - c->delta = (MSADPCMAdaptationTable[(int)code] * c->delta) >> 8; - - if (c->delta < 16) - c->delta = 16; - - return (int16)predictor; -} - -int MS_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { - int samples; - byte data; - int i = 0; - - samples = 0; - - while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { - if (_blockPos[0] == _blockAlign) { - // read block header - for (i = 0; i < _channels; i++) { - _status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6); - _status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor]; - _status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor]; - } - - for (i = 0; i < _channels; i++) - _status.ch[i].delta = _stream->readSint16LE(); - - for (i = 0; i < _channels; i++) - _status.ch[i].sample1 = _stream->readSint16LE(); - - for (i = 0; i < _channels; i++) - buffer[samples++] = _status.ch[i].sample2 = _stream->readSint16LE(); - - for (i = 0; i < _channels; i++) - buffer[samples++] = _status.ch[i].sample1; - - _blockPos[0] = _channels * 7; - } - - for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { - data = _stream->readByte(); - _blockPos[0]++; - buffer[samples] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f); - buffer[samples + 1] = decodeMS(&_status.ch[_channels - 1], data & 0x0f); - } - } - - return samples; -} - - - -#pragma mark - - - -class Tinsel_ADPCMStream : public ADPCMStream { -protected: - struct { - // Tinsel - double predictor; - double K0, K1; - double d0, d1; - } _status; - - void reset() { - ADPCMStream::reset(); - memset(&_status, 0, sizeof(_status)); - } - - int16 decodeTinsel(int16, double); - void readBufferTinselHeader(); - -public: - Tinsel_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) - : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { - - if (blockAlign == 0) - error("Tinsel_ADPCMStream(): blockAlign isn't specified"); - - if (channels != 1) - error("Tinsel_ADPCMStream(): Tinsel ADPCM only supports mono"); - - memset(&_status, 0, sizeof(_status)); - } - -}; - -static const double TinselFilterTable[4][2] = { - {0, 0 }, - {0.9375, 0}, - {1.796875, -0.8125}, - {1.53125, -0.859375} -}; - -void Tinsel_ADPCMStream::readBufferTinselHeader() { - uint8 start = _stream->readByte(); - uint8 filterVal = (start & 0xC0) >> 6; - - if ((start & 0x20) != 0) { - //Lower 6 bit are negative - - // Negate - start = ~(start | 0xC0) + 1; - - _status.predictor = 1 << start; - } else { - // Lower 6 bit are positive - - // Truncate - start &= 0x1F; - - _status.predictor = ((double) 1.0) / (1 << start); - } - - _status.K0 = TinselFilterTable[filterVal][0]; - _status.K1 = TinselFilterTable[filterVal][1]; -} - -int16 Tinsel_ADPCMStream::decodeTinsel(int16 code, double eVal) { - double sample; - - sample = (double) code; - sample *= eVal * _status.predictor; - sample += (_status.d0 * _status.K0) + (_status.d1 * _status.K1); - - _status.d1 = _status.d0; - _status.d0 = sample; - - return (int16) CLIP(sample, -32768.0, 32767.0); -} - -class Tinsel4_ADPCMStream : public Tinsel_ADPCMStream { -public: - Tinsel4_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) - : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {} - - virtual int readBuffer(int16 *buffer, const int numSamples); -}; - -int Tinsel4_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { - int samples; - uint16 data; - const double eVal = 1.142822265; - - samples = 0; - - assert(numSamples % 2 == 0); - - while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { - if (_blockPos[0] == _blockAlign) { - readBufferTinselHeader(); - _blockPos[0] = 0; - } - - for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2, _blockPos[0]++) { - // Read 1 byte = 8 bits = two 4 bit blocks - data = _stream->readByte(); - buffer[samples] = decodeTinsel((data << 8) & 0xF000, eVal); - buffer[samples+1] = decodeTinsel((data << 12) & 0xF000, eVal); - } - } - - return samples; -} - -class Tinsel6_ADPCMStream : public Tinsel_ADPCMStream { -protected: - uint8 _chunkPos; - uint16 _chunkData; - - void reset() { - ADPCMStream::reset(); - _chunkPos = 0; - _chunkData = 0; - } - -public: - Tinsel6_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) - : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { - _chunkPos = 0; - _chunkData = 0; - } - - virtual int readBuffer(int16 *buffer, const int numSamples); -}; - -int Tinsel6_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { - int samples; - const double eVal = 1.032226562; - - samples = 0; - - while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { - if (_blockPos[0] == _blockAlign) { - readBufferTinselHeader(); - _blockPos[0] = 0; - _chunkPos = 0; - } - - for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples++, _chunkPos = (_chunkPos + 1) % 4) { - - switch (_chunkPos) { - case 0: - _chunkData = _stream->readByte(); - buffer[samples] = decodeTinsel((_chunkData << 8) & 0xFC00, eVal); - break; - case 1: - _chunkData = (_chunkData << 8) | (_stream->readByte()); - buffer[samples] = decodeTinsel((_chunkData << 6) & 0xFC00, eVal); - _blockPos[0]++; - break; - case 2: - _chunkData = (_chunkData << 8) | (_stream->readByte()); - buffer[samples] = decodeTinsel((_chunkData << 4) & 0xFC00, eVal); - _blockPos[0]++; - break; - case 3: - _chunkData = (_chunkData << 8); - buffer[samples] = decodeTinsel((_chunkData << 2) & 0xFC00, eVal); - _blockPos[0]++; - break; - } - - } - - } - - return samples; -} - -class Tinsel8_ADPCMStream : public Tinsel_ADPCMStream { -public: - Tinsel8_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) - : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {} - - virtual int readBuffer(int16 *buffer, const int numSamples); -}; - -int Tinsel8_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { - int samples; - byte data; - const double eVal = 1.007843258; - - samples = 0; - - while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { - if (_blockPos[0] == _blockAlign) { - readBufferTinselHeader(); - _blockPos[0] = 0; - } - - for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples++, _blockPos[0]++) { - // Read 1 byte = 8 bits = one 8 bit block - data = _stream->readByte(); - buffer[samples] = decodeTinsel(data << 8, eVal); - } - } - - return samples; -} - - -#pragma mark - - -// Duck DK3 IMA ADPCM Decoder -// Based on FFmpeg's decoder and http://wiki.multimedia.cx/index.php?title=Duck_DK3_IMA_ADPCM - -class DK3_ADPCMStream : public Ima_ADPCMStream { -protected: - - void reset() { - Ima_ADPCMStream::reset(); - _topNibble = false; - } - -public: - DK3_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) - : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { - - // DK3 only works as a stereo stream - assert(channels == 2); - _topNibble = false; - } - - virtual int readBuffer(int16 *buffer, const int numSamples); - -private: - byte _nibble, _lastByte; - bool _topNibble; -}; - -#define DK3_READ_NIBBLE() \ -do { \ - if (_topNibble) { \ - _nibble = _lastByte >> 4; \ - _topNibble = false; \ - } else { \ - if (_stream->pos() >= _endpos) \ - break; \ - if ((_stream->pos() % _blockAlign) == 0) \ - continue; \ - _lastByte = _stream->readByte(); \ - _nibble = _lastByte & 0xf; \ - _topNibble = true; \ - } \ -} while (0) - - -int DK3_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { - int samples = 0; - - assert((numSamples % 4) == 0); - - while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { - if ((_stream->pos() % _blockAlign) == 0) { - _stream->readUint16LE(); // Unknown - uint16 rate = _stream->readUint16LE(); // Copy of rate - _stream->skip(6); // Unknown - // Get predictor for both sum/diff channels - _status.ima_ch[0].last = _stream->readSint16LE(); - _status.ima_ch[1].last = _stream->readSint16LE(); - // Get index for both sum/diff channels - _status.ima_ch[0].stepIndex = _stream->readByte(); - _status.ima_ch[1].stepIndex = _stream->readByte(); - - if (_stream->eos()) - break; - - // Sanity check - assert(rate == getRate()); - } - - DK3_READ_NIBBLE(); - decodeIMA(_nibble, 0); - - DK3_READ_NIBBLE(); - decodeIMA(_nibble, 1); - - buffer[samples++] = _status.ima_ch[0].last + _status.ima_ch[1].last; - buffer[samples++] = _status.ima_ch[0].last - _status.ima_ch[1].last; - - DK3_READ_NIBBLE(); - decodeIMA(_nibble, 0); - - buffer[samples++] = _status.ima_ch[0].last + _status.ima_ch[1].last; - buffer[samples++] = _status.ima_ch[0].last - _status.ima_ch[1].last; - } - - return samples; -} - - -#pragma mark - - - -// adjust the step for use on the next sample. -int16 ADPCMStream::stepAdjust(byte code) { - static const int16 adjusts[] = {-1, -1, -1, -1, 2, 4, 6, 8}; - - return adjusts[code & 0x07]; -} - -static const uint16 imaStepTable[89] = { - 7, 8, 9, 10, 11, 12, 13, 14, - 16, 17, 19, 21, 23, 25, 28, 31, - 34, 37, 41, 45, 50, 55, 60, 66, - 73, 80, 88, 97, 107, 118, 130, 143, - 157, 173, 190, 209, 230, 253, 279, 307, - 337, 371, 408, 449, 494, 544, 598, 658, - 724, 796, 876, 963, 1060, 1166, 1282, 1411, - 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, - 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, - 7132, 7845, 8630, 9493,10442,11487,12635,13899, - 15289,16818,18500,20350,22385,24623,27086,29794, - 32767 -}; - -int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) { - int32 E = (2 * (code & 0x7) + 1) * imaStepTable[_status.ima_ch[channel].stepIndex] / 8; - int32 diff = (code & 0x08) ? -E : E; - int32 samp = CLIP(_status.ima_ch[channel].last + diff, -32768, 32767); - - _status.ima_ch[channel].last = samp; - _status.ima_ch[channel].stepIndex += stepAdjust(code); - _status.ima_ch[channel].stepIndex = CLIP(_status.ima_ch[channel].stepIndex, 0, ARRAYSIZE(imaStepTable) - 1); - - return samp; -} - -RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) { - // If size is 0, report the entire size of the stream - if (!size) - size = stream->size(); - - switch (type) { - case kADPCMOki: - return new Oki_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); - case kADPCMMSIma: - return new MSIma_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); - case kADPCMMSImaLastExpress: - return new MSIma_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign, true); - case kADPCMMS: - return new MS_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); - case kADPCMTinsel4: - return new Tinsel4_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); - case kADPCMTinsel6: - return new Tinsel6_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); - case kADPCMTinsel8: - return new Tinsel8_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); - case kADPCMIma: - return new Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); - case kADPCMApple: - return new Apple_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); - case kADPCMDK3: - return new DK3_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); - default: - error("Unsupported ADPCM encoding"); - break; - } -} - -} // End of namespace Audio diff --git a/sound/decoders/adpcm.h b/sound/decoders/adpcm.h deleted file mode 100644 index 38ec870a27..0000000000 --- a/sound/decoders/adpcm.h +++ /dev/null @@ -1,90 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - agos - * - lastexpress - * - mohawk - * - saga - * - scumm - * - tinsel - */ - -#ifndef SOUND_ADPCM_H -#define SOUND_ADPCM_H - -#include "common/scummsys.h" -#include "common/stream.h" - - -namespace Audio { - -class AudioStream; -class RewindableAudioStream; - -// There are several types of ADPCM encoding, only some are supported here -// For all the different encodings, refer to: -// http://wiki.multimedia.cx/index.php?title=Category:ADPCM_Audio_Codecs -// Usually, if the audio stream we're trying to play has the FourCC header -// string intact, it's easy to discern which encoding is used -enum typesADPCM { - kADPCMOki, // Dialogic/Oki ADPCM (aka VOX) - kADPCMMSIma, // Microsoft IMA ADPCM - kADPCMMSImaLastExpress, // Microsoft IMA ADPCM (with inverted samples) - kADPCMMS, // Microsoft ADPCM - kADPCMTinsel4, // 4-bit ADPCM used by the Tinsel engine - kADPCMTinsel6, // 6-bit ADPCM used by the Tinsel engine - kADPCMTinsel8, // 8-bit ADPCM used by the Tinsel engine - kADPCMIma, // Standard IMA ADPCM - kADPCMApple, // Apple QuickTime IMA ADPCM - kADPCMDK3 // Duck DK3 IMA ADPCM -}; - -/** - * Takes an input stream containing ADPCM compressed sound data and creates - * an RewindableAudioStream from that. - * - * @param stream the SeekableReadStream from which to read the ADPCM data - * @param disposeAfterUse whether to delete the stream after use - * @param size how many bytes to read from the stream (0 = all) - * @param type the compression type used - * @param rate the sampling rate - * @param channels the number of channels - * @param blockAlign block alignment ??? - * @return a new RewindableAudioStream, or NULL, if an error occurred - */ -RewindableAudioStream *makeADPCMStream( - Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse, - uint32 size, typesADPCM type, - int rate = 22050, - int channels = 2, - uint32 blockAlign = 0); - -} // End of namespace Audio - -#endif diff --git a/sound/decoders/aiff.cpp b/sound/decoders/aiff.cpp deleted file mode 100644 index ce8c6ad32c..0000000000 --- a/sound/decoders/aiff.cpp +++ /dev/null @@ -1,186 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/* - * The code in this file is based on information found at - * http://www.borg.com/~jglatt/tech/aiff.htm - * - * We currently only implement uncompressed AIFF. If we ever need AIFF-C, SoX - * (http://sox.sourceforge.net) may be a good place to start from. - */ - -#include "common/endian.h" -#include "common/util.h" -#include "common/stream.h" - -#include "sound/decoders/aiff.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" - -namespace Audio { - -uint32 readExtended(Common::SeekableReadStream &stream) { - // The sample rate is stored as an "80 bit IEEE Standard 754 floating - // point number (Standard Apple Numeric Environment [SANE] data type - // Extended). - - byte buf[10]; - uint32 mantissa; - uint32 last = 0; - byte exp; - - stream.read(buf, 10); - mantissa = READ_BE_UINT32(buf + 2); - exp = 30 - buf[1]; - - while (exp--) { - last = mantissa; - mantissa >>= 1; - } - - if (last & 0x00000001) - mantissa++; - - return mantissa; -} - -bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags) { - byte buf[4]; - - stream.read(buf, 4); - if (memcmp(buf, "FORM", 4) != 0) { - warning("loadAIFFFromStream: No 'FORM' header"); - return false; - } - - stream.readUint32BE(); - - // This could be AIFC, but we don't handle that case. - - stream.read(buf, 4); - if (memcmp(buf, "AIFF", 4) != 0) { - warning("loadAIFFFromStream: No 'AIFF' header"); - return false; - } - - // From here on, we only care about the COMM and SSND chunks, which are - // the only required chunks. - - bool foundCOMM = false; - bool foundSSND = false; - - uint16 numChannels = 0, bitsPerSample = 0; - uint32 numSampleFrames = 0, offset = 0, blockSize = 0, soundOffset = 0; - - while (!(foundCOMM && foundSSND) && !stream.err() && !stream.eos()) { - uint32 length, pos; - - stream.read(buf, 4); - length = stream.readUint32BE(); - pos = stream.pos(); - - if (memcmp(buf, "COMM", 4) == 0) { - foundCOMM = true; - numChannels = stream.readUint16BE(); - numSampleFrames = stream.readUint32BE(); - bitsPerSample = stream.readUint16BE(); - rate = readExtended(stream); - size = numSampleFrames * numChannels * (bitsPerSample / 8); - } else if (memcmp(buf, "SSND", 4) == 0) { - foundSSND = true; - offset = stream.readUint32BE(); - blockSize = stream.readUint32BE(); - soundOffset = stream.pos(); - } - - stream.seek(pos + length); - } - - if (!foundCOMM) { - warning("loadAIFFFromStream: Cound not find 'COMM' chunk"); - return false; - } - - if (!foundSSND) { - warning("loadAIFFFromStream: Cound not find 'SSND' chunk"); - return false; - } - - // We only implement a subset of the AIFF standard. - - if (numChannels < 1 || numChannels > 2) { - warning("loadAIFFFromStream: Only 1 or 2 channels are supported, not %d", numChannels); - return false; - } - - if (bitsPerSample != 8 && bitsPerSample != 16) { - warning("loadAIFFFromStream: Only 8 or 16 bits per sample are supported, not %d", bitsPerSample); - return false; - } - - if (offset != 0 || blockSize != 0) { - warning("loadAIFFFromStream: Block-aligned data is not supported"); - return false; - } - - // Samples are always signed, and big endian. - - flags = 0; - if (bitsPerSample == 16) - flags |= Audio::FLAG_16BITS; - if (numChannels == 2) - flags |= Audio::FLAG_STEREO; - - stream.seek(soundOffset); - - // Stream now points at the sample data - - return true; -} - -SeekableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse) { - int size, rate; - byte *data, flags; - - if (!loadAIFFFromStream(*stream, size, rate, flags)) { - if (disposeAfterUse == DisposeAfterUse::YES) - delete stream; - return 0; - } - - data = (byte *)malloc(size); - assert(data); - stream->read(data, size); - - if (disposeAfterUse == DisposeAfterUse::YES) - delete stream; - - // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. - return makeRawStream(data, size, rate, flags); -} - -} // End of namespace Audio diff --git a/sound/decoders/aiff.h b/sound/decoders/aiff.h deleted file mode 100644 index 06c56ecd38..0000000000 --- a/sound/decoders/aiff.h +++ /dev/null @@ -1,71 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - saga - * - sci - * - sword1 - */ - -#ifndef SOUND_AIFF_H -#define SOUND_AIFF_H - -#include "common/scummsys.h" -#include "common/types.h" - -namespace Common { class SeekableReadStream; } - -namespace Audio { - -class SeekableAudioStream; - -/** - * Try to load an AIFF from the given seekable stream. Returns true if - * successful. In that case, the stream's seek position will be set to the - * start of the audio data, and size, rate and flags contain information - * necessary for playback. Currently this function only supports uncompressed - * raw PCM data as well as IMA ADPCM. - */ -extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags); - -/** - * Try to load an AIFF from the given seekable stream and create an AudioStream - * from that data. - * - * This function uses loadAIFFFromStream() internally. - * - * @param stream the SeekableReadStream from which to read the AIFF data - * @param disposeAfterUse whether to delete the stream after use - * @return a new SeekableAudioStream, or NULL, if an error occurred - */ -SeekableAudioStream *makeAIFFStream( - Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse); - -} // End of namespace Audio - -#endif diff --git a/sound/decoders/flac.cpp b/sound/decoders/flac.cpp deleted file mode 100644 index 080141f224..0000000000 --- a/sound/decoders/flac.cpp +++ /dev/null @@ -1,745 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -// Disable symbol overrides for FILE as that is used in FLAC headers -#define FORBIDDEN_SYMBOL_EXCEPTION_FILE - -#include "sound/decoders/flac.h" - -#ifdef USE_FLAC - -#include "common/debug.h" -#include "common/stream.h" -#include "common/util.h" - -#include "sound/audiostream.h" - -#define FLAC__NO_DLL // that MS-magic gave me headaches - just link the library you like -#include - - -// check if we have FLAC >= 1.1.3; LEGACY_FLAC code can be removed once FLAC-1.1.3 propagates everywhere -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT < 8 -#define LEGACY_FLAC -#else -#undef LEGACY_FLAC -#endif - - -#ifdef LEGACY_FLAC - -// Before FLAC 1.1.3, we needed to use the stream decoder API. -#include -typedef uint FLAC_size_t; - -#else - -// With FLAC 1.1.3, the stream decoder API was merged into the regular -// stream API. In order to stay compatible with older FLAC versions, we -// simply add some typedefs and #ifdefs to map between the old and new API. -// We use the typedefs (instead of only #defines) in order to somewhat -// improve the readability of the code. - -#include -typedef size_t FLAC_size_t; -// Add aliases for the old names -typedef FLAC__StreamDecoderState FLAC__SeekableStreamDecoderState; -typedef FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus; -typedef FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus; -typedef FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus; -typedef FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus; -typedef FLAC__StreamDecoder FLAC__SeekableStreamDecoder; - -#endif - - -namespace Audio { - -#pragma mark - -#pragma mark --- FLAC stream --- -#pragma mark - - -static const uint MAX_OUTPUT_CHANNELS = 2; - - -class FLACStream : public SeekableAudioStream { -protected: - Common::SeekableReadStream *_inStream; - bool _disposeAfterUse; - - ::FLAC__SeekableStreamDecoder *_decoder; - - /** Header of the stream */ - FLAC__StreamMetadata_StreamInfo _streaminfo; - - /** index + 1(!) of the last sample to be played */ - FLAC__uint64 _lastSample; - - /** total play time */ - Timestamp _length; - - /** true if the last sample was decoded from the FLAC-API - there might still be data in the buffer */ - bool _lastSampleWritten; - - typedef int16 SampleType; - enum { BUFTYPE_BITS = 16 }; - - enum { - // Maximal buffer size. According to the FLAC format specification, the block size is - // a 16 bit value (in fact it seems the maximal block size is 32768, but we play it safe). - BUFFER_SIZE = 65536 - }; - - struct { - SampleType bufData[BUFFER_SIZE]; - SampleType *bufReadPos; - uint bufFill; - } _sampleCache; - - SampleType *_outBuffer; - uint _requestedSamples; - - typedef void (*PFCONVERTBUFFERS)(SampleType*, const FLAC__int32*[], uint, const uint, const uint8); - PFCONVERTBUFFERS _methodConvertBuffers; - - -public: - FLACStream(Common::SeekableReadStream *inStream, bool dispose); - virtual ~FLACStream(); - - int readBuffer(int16 *buffer, const int numSamples); - - bool isStereo() const { return _streaminfo.channels >= 2; } - int getRate() const { return _streaminfo.sample_rate; } - bool endOfData() const { - // End of data is reached if there either is no valid stream data available, - // or if we reached the last sample and completely emptied the sample cache. - return _streaminfo.channels == 0 || (_lastSampleWritten && _sampleCache.bufFill == 0); - } - - bool seek(const Timestamp &where); - Timestamp getLength() const { return _length; } - - bool isStreamDecoderReady() const { return getStreamDecoderState() == FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC; } -protected: - uint getChannels() const { return MIN(_streaminfo.channels, MAX_OUTPUT_CHANNELS); } - - bool allocateBuffer(uint minSamples); - - inline FLAC__StreamDecoderState getStreamDecoderState() const; - - inline bool processSingleBlock(); - inline bool processUntilEndOfMetadata(); - bool seekAbsolute(FLAC__uint64 sample); - - inline ::FLAC__SeekableStreamDecoderReadStatus callbackRead(FLAC__byte buffer[], FLAC_size_t *bytes); - inline ::FLAC__SeekableStreamDecoderSeekStatus callbackSeek(FLAC__uint64 absoluteByteOffset); - inline ::FLAC__SeekableStreamDecoderTellStatus callbackTell(FLAC__uint64 *absoluteByteOffset); - inline ::FLAC__SeekableStreamDecoderLengthStatus callbackLength(FLAC__uint64 *streamLength); - inline bool callbackEOF(); - inline ::FLAC__StreamDecoderWriteStatus callbackWrite(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]); - inline void callbackMetadata(const ::FLAC__StreamMetadata *metadata); - inline void callbackError(::FLAC__StreamDecoderErrorStatus status); - -private: - static ::FLAC__SeekableStreamDecoderReadStatus callWrapRead(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], FLAC_size_t *bytes, void *clientData); - static ::FLAC__SeekableStreamDecoderSeekStatus callWrapSeek(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absoluteByteOffset, void *clientData); - static ::FLAC__SeekableStreamDecoderTellStatus callWrapTell(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absoluteByteOffset, void *clientData); - static ::FLAC__SeekableStreamDecoderLengthStatus callWrapLength(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *streamLength, void *clientData); - static FLAC__bool callWrapEOF(const ::FLAC__SeekableStreamDecoder *decoder, void *clientData); - static ::FLAC__StreamDecoderWriteStatus callWrapWrite(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *clientData); - static void callWrapMetadata(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__StreamMetadata *metadata, void *clientData); - static void callWrapError(const ::FLAC__SeekableStreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *clientData); - - void setBestConvertBufferMethod(); - static void convertBuffersGeneric(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); - static void convertBuffersStereoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); - static void convertBuffersStereo8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); - static void convertBuffersMonoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); - static void convertBuffersMono8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); -}; - -FLACStream::FLACStream(Common::SeekableReadStream *inStream, bool dispose) -#ifdef LEGACY_FLAC - : _decoder(::FLAC__seekable_stream_decoder_new()), -#else - : _decoder(::FLAC__stream_decoder_new()), -#endif - _inStream(inStream), - _disposeAfterUse(dispose), - _length(0, 1000), _lastSample(0), - _outBuffer(NULL), _requestedSamples(0), _lastSampleWritten(false), - _methodConvertBuffers(&FLACStream::convertBuffersGeneric) -{ - assert(_inStream); - memset(&_streaminfo, 0, sizeof(_streaminfo)); - - _sampleCache.bufReadPos = NULL; - _sampleCache.bufFill = 0; - - _methodConvertBuffers = &FLACStream::convertBuffersGeneric; - - bool success; -#ifdef LEGACY_FLAC - ::FLAC__seekable_stream_decoder_set_read_callback(_decoder, &FLACStream::callWrapRead); - ::FLAC__seekable_stream_decoder_set_seek_callback(_decoder, &FLACStream::callWrapSeek); - ::FLAC__seekable_stream_decoder_set_tell_callback(_decoder, &FLACStream::callWrapTell); - ::FLAC__seekable_stream_decoder_set_length_callback(_decoder, &FLACStream::callWrapLength); - ::FLAC__seekable_stream_decoder_set_eof_callback(_decoder, &FLACStream::callWrapEOF); - ::FLAC__seekable_stream_decoder_set_write_callback(_decoder, &FLACStream::callWrapWrite); - ::FLAC__seekable_stream_decoder_set_metadata_callback(_decoder, &FLACStream::callWrapMetadata); - ::FLAC__seekable_stream_decoder_set_error_callback(_decoder, &FLACStream::callWrapError); - ::FLAC__seekable_stream_decoder_set_client_data(_decoder, (void*)this); - - success = (::FLAC__seekable_stream_decoder_init(_decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK); -#else - success = (::FLAC__stream_decoder_init_stream( - _decoder, - &FLACStream::callWrapRead, - &FLACStream::callWrapSeek, - &FLACStream::callWrapTell, - &FLACStream::callWrapLength, - &FLACStream::callWrapEOF, - &FLACStream::callWrapWrite, - &FLACStream::callWrapMetadata, - &FLACStream::callWrapError, - (void*)this - ) == FLAC__STREAM_DECODER_INIT_STATUS_OK); -#endif - if (success) { - if (processUntilEndOfMetadata() && _streaminfo.channels > 0) { - _lastSample = _streaminfo.total_samples + 1; - _length = Timestamp(0, _lastSample - 1, getRate()); - return; // no error occurred - } - } - - warning("FLACStream: could not create audio stream"); -} - -FLACStream::~FLACStream() { - if (_decoder != NULL) { -#ifdef LEGACY_FLAC - (void) ::FLAC__seekable_stream_decoder_finish(_decoder); - ::FLAC__seekable_stream_decoder_delete(_decoder); -#else - (void) ::FLAC__stream_decoder_finish(_decoder); - ::FLAC__stream_decoder_delete(_decoder); -#endif - } - if (_disposeAfterUse) - delete _inStream; -} - -inline FLAC__StreamDecoderState FLACStream::getStreamDecoderState() const { - assert(_decoder != NULL); -#ifdef LEGACY_FLAC - return ::FLAC__seekable_stream_decoder_get_stream_decoder_state(_decoder); -#else - return ::FLAC__stream_decoder_get_state(_decoder); -#endif -} - -inline bool FLACStream::processSingleBlock() { - assert(_decoder != NULL); -#ifdef LEGACY_FLAC - return 0 != ::FLAC__seekable_stream_decoder_process_single(_decoder); -#else - return 0 != ::FLAC__stream_decoder_process_single(_decoder); -#endif -} - -inline bool FLACStream::processUntilEndOfMetadata() { - assert(_decoder != NULL); -#ifdef LEGACY_FLAC - return 0 != ::FLAC__seekable_stream_decoder_process_until_end_of_metadata(_decoder); -#else - return 0 != ::FLAC__stream_decoder_process_until_end_of_metadata(_decoder); -#endif -} - -bool FLACStream::seekAbsolute(FLAC__uint64 sample) { - assert(_decoder != NULL); -#ifdef LEGACY_FLAC - const bool result = (0 != ::FLAC__seekable_stream_decoder_seek_absolute(_decoder, sample)); -#else - const bool result = (0 != ::FLAC__stream_decoder_seek_absolute(_decoder, sample)); -#endif - if (result) { - _lastSampleWritten = (_lastSample != 0 && sample >= _lastSample); // only set if we are SURE - } - return result; -} - -bool FLACStream::seek(const Timestamp &where) { - _sampleCache.bufFill = 0; - _sampleCache.bufReadPos = NULL; - // FLAC uses the sample pair number, thus we always use "false" for the isStereo parameter - // of the convertTimeToStreamPos helper. - return seekAbsolute((FLAC__uint64)convertTimeToStreamPos(where, getRate(), false).totalNumberOfFrames()); -} - -int FLACStream::readBuffer(int16 *buffer, const int numSamples) { - const uint numChannels = getChannels(); - - if (numChannels == 0) { - warning("FLACStream: Stream not successfully initialised, cant playback"); - return -1; // streaminfo wasnt read! - } - - assert(numSamples % numChannels == 0); // must be multiple of channels! - assert(buffer != NULL); - assert(_outBuffer == NULL); - assert(_requestedSamples == 0); - - _outBuffer = buffer; - _requestedSamples = numSamples; - - // If there is still data in our buffer from the last time around, - // copy that first. - if (_sampleCache.bufFill > 0) { - assert(_sampleCache.bufReadPos >= _sampleCache.bufData); - assert(_sampleCache.bufFill % numChannels == 0); - - const uint copySamples = MIN((uint)numSamples, _sampleCache.bufFill); - memcpy(buffer, _sampleCache.bufReadPos, copySamples*sizeof(buffer[0])); - - _outBuffer = buffer + copySamples; - _requestedSamples = numSamples - copySamples; - _sampleCache.bufReadPos += copySamples; - _sampleCache.bufFill -= copySamples; - } - - bool decoderOk = true; - - FLAC__StreamDecoderState state = getStreamDecoderState(); - - // Keep poking FLAC to process more samples until we completely satisfied the request - // respectively until we run out of data. - while (!_lastSampleWritten && _requestedSamples > 0 && state == FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC) { - assert(_sampleCache.bufFill == 0); - assert(_requestedSamples % numChannels == 0); - processSingleBlock(); - state = getStreamDecoderState(); - - if (state == FLAC__STREAM_DECODER_END_OF_STREAM) - _lastSampleWritten = true; - } - - // Error handling - switch (state) { - case FLAC__STREAM_DECODER_END_OF_STREAM: - _lastSampleWritten = true; - break; - case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: - break; - default: - decoderOk = false; - warning("FLACStream: An error occurred while decoding. DecoderState is: %s", - FLAC__StreamDecoderStateString[getStreamDecoderState()]); - } - - // Compute how many samples we actually produced - const int samples = (int)(_outBuffer - buffer); - assert(samples % numChannels == 0); - - _outBuffer = NULL; // basically unnecessary, only for the purpose of the asserts - _requestedSamples = 0; // basically unnecessary, only for the purpose of the asserts - - return decoderOk ? samples : -1; -} - -inline ::FLAC__SeekableStreamDecoderReadStatus FLACStream::callbackRead(FLAC__byte buffer[], FLAC_size_t *bytes) { - if (*bytes == 0) { -#ifdef LEGACY_FLAC - return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; /* abort to avoid a deadlock */ -#else - return FLAC__STREAM_DECODER_READ_STATUS_ABORT; /* abort to avoid a deadlock */ -#endif - } - - const uint32 bytesRead = _inStream->read(buffer, *bytes); - - if (bytesRead == 0) { -#ifdef LEGACY_FLAC - return _inStream->eos() ? FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK : FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; -#else - return _inStream->eos() ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_ABORT; -#endif - } - - *bytes = static_cast(bytesRead); -#ifdef LEGACY_FLAC - return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; -#else - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; -#endif -} - -void FLACStream::setBestConvertBufferMethod() { - PFCONVERTBUFFERS tempMethod = &FLACStream::convertBuffersGeneric; - - const uint numChannels = getChannels(); - const uint8 numBits = (uint8)_streaminfo.bits_per_sample; - - assert(numChannels >= 1); - assert(numBits >= 4 && numBits <=32); - - if (numChannels == 1) { - if (numBits == 8) - tempMethod = &FLACStream::convertBuffersMono8Bit; - if (numBits == BUFTYPE_BITS) - tempMethod = &FLACStream::convertBuffersMonoNS; - } else if (numChannels == 2) { - if (numBits == 8) - tempMethod = &FLACStream::convertBuffersStereo8Bit; - if (numBits == BUFTYPE_BITS) - tempMethod = &FLACStream::convertBuffersStereoNS; - } /* else ... */ - - _methodConvertBuffers = tempMethod; -} - -// 1 channel, no scaling -void FLACStream::convertBuffersMonoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { - assert(numChannels == 1); - assert(numBits == BUFTYPE_BITS); - - FLAC__int32 const* inChannel1 = inChannels[0]; - - while (numSamples >= 4) { - bufDestination[0] = static_cast(inChannel1[0]); - bufDestination[1] = static_cast(inChannel1[1]); - bufDestination[2] = static_cast(inChannel1[2]); - bufDestination[3] = static_cast(inChannel1[3]); - bufDestination += 4; - inChannel1 += 4; - numSamples -= 4; - } - - for (; numSamples > 0; --numSamples) { - *bufDestination++ = static_cast(*inChannel1++); - } - - inChannels[0] = inChannel1; - assert(numSamples == 0); // dint copy too many samples -} - -// 1 channel, scaling from 8Bit -void FLACStream::convertBuffersMono8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { - assert(numChannels == 1); - assert(numBits == 8); - assert(8 < BUFTYPE_BITS); - - FLAC__int32 const* inChannel1 = inChannels[0]; - - while (numSamples >= 4) { - bufDestination[0] = static_cast(inChannel1[0]) << (BUFTYPE_BITS - 8); - bufDestination[1] = static_cast(inChannel1[1]) << (BUFTYPE_BITS - 8); - bufDestination[2] = static_cast(inChannel1[2]) << (BUFTYPE_BITS - 8); - bufDestination[3] = static_cast(inChannel1[3]) << (BUFTYPE_BITS - 8); - bufDestination += 4; - inChannel1 += 4; - numSamples -= 4; - } - - for (; numSamples > 0; --numSamples) { - *bufDestination++ = static_cast(*inChannel1++) << (BUFTYPE_BITS - 8); - } - - inChannels[0] = inChannel1; - assert(numSamples == 0); // dint copy too many samples -} - -// 2 channels, no scaling -void FLACStream::convertBuffersStereoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { - assert(numChannels == 2); - assert(numBits == BUFTYPE_BITS); - assert(numSamples % 2 == 0); // must be integral multiply of channels - - - FLAC__int32 const* inChannel1 = inChannels[0]; // Left Channel - FLAC__int32 const* inChannel2 = inChannels[1]; // Right Channel - - while (numSamples >= 2*2) { - bufDestination[0] = static_cast(inChannel1[0]); - bufDestination[1] = static_cast(inChannel2[0]); - bufDestination[2] = static_cast(inChannel1[1]); - bufDestination[3] = static_cast(inChannel2[1]); - bufDestination += 2 * 2; - inChannel1 += 2; - inChannel2 += 2; - numSamples -= 2 * 2; - } - - while (numSamples > 0) { - bufDestination[0] = static_cast(*inChannel1++); - bufDestination[1] = static_cast(*inChannel2++); - bufDestination += 2; - numSamples -= 2; - } - - inChannels[0] = inChannel1; - inChannels[1] = inChannel2; - assert(numSamples == 0); // dint copy too many samples -} - -// 2 channels, scaling from 8Bit -void FLACStream::convertBuffersStereo8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { - assert(numChannels == 2); - assert(numBits == 8); - assert(numSamples % 2 == 0); // must be integral multiply of channels - assert(8 < BUFTYPE_BITS); - - FLAC__int32 const* inChannel1 = inChannels[0]; // Left Channel - FLAC__int32 const* inChannel2 = inChannels[1]; // Right Channel - - while (numSamples >= 2*2) { - bufDestination[0] = static_cast(inChannel1[0]) << (BUFTYPE_BITS - 8); - bufDestination[1] = static_cast(inChannel2[0]) << (BUFTYPE_BITS - 8); - bufDestination[2] = static_cast(inChannel1[1]) << (BUFTYPE_BITS - 8); - bufDestination[3] = static_cast(inChannel2[1]) << (BUFTYPE_BITS - 8); - bufDestination += 2 * 2; - inChannel1 += 2; - inChannel2 += 2; - numSamples -= 2 * 2; - } - - while (numSamples > 0) { - bufDestination[0] = static_cast(*inChannel1++) << (BUFTYPE_BITS - 8); - bufDestination[1] = static_cast(*inChannel2++) << (BUFTYPE_BITS - 8); - bufDestination += 2; - numSamples -= 2; - } - - inChannels[0] = inChannel1; - inChannels[1] = inChannel2; - assert(numSamples == 0); // dint copy too many samples -} - -// all Purpose-conversion - slowest of em all -void FLACStream::convertBuffersGeneric(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { - assert(numSamples % numChannels == 0); // must be integral multiply of channels - - if (numBits < BUFTYPE_BITS) { - const uint8 kPower = (uint8)(BUFTYPE_BITS - numBits); - - for (; numSamples > 0; numSamples -= numChannels) { - for (uint i = 0; i < numChannels; ++i) - *bufDestination++ = static_cast(*(inChannels[i]++)) << kPower; - } - } else if (numBits > BUFTYPE_BITS) { - const uint8 kPower = (uint8)(numBits - BUFTYPE_BITS); - - for (; numSamples > 0; numSamples -= numChannels) { - for (uint i = 0; i < numChannels; ++i) - *bufDestination++ = static_cast(*(inChannels[i]++) >> kPower); - } - } else { - for (; numSamples > 0; numSamples -= numChannels) { - for (uint i = 0; i < numChannels; ++i) - *bufDestination++ = static_cast(*(inChannels[i]++)); - } - } - - assert(numSamples == 0); // dint copy too many samples -} - -inline ::FLAC__StreamDecoderWriteStatus FLACStream::callbackWrite(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]) { - assert(frame->header.channels == _streaminfo.channels); - assert(frame->header.sample_rate == _streaminfo.sample_rate); - assert(frame->header.bits_per_sample == _streaminfo.bits_per_sample); - assert(frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER || _streaminfo.min_blocksize == _streaminfo.max_blocksize); - - // We require that either the sample cache is empty, or that no samples were requested - assert(_sampleCache.bufFill == 0 || _requestedSamples == 0); - - uint numSamples = frame->header.blocksize; - const uint numChannels = getChannels(); - const uint8 numBits = (uint8)_streaminfo.bits_per_sample; - - assert(_requestedSamples % numChannels == 0); // must be integral multiply of channels - - const FLAC__uint64 firstSampleNumber = (frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER) ? - frame->header.number.sample_number : (static_cast(frame->header.number.frame_number)) * _streaminfo.max_blocksize; - - // Check whether we are about to reach beyond the last sample we are supposed to play. - if (_lastSample != 0 && firstSampleNumber + numSamples >= _lastSample) { - numSamples = (uint)(firstSampleNumber >= _lastSample ? 0 : _lastSample - firstSampleNumber); - _lastSampleWritten = true; - } - - // The value in _requestedSamples counts raw samples, so if there are more than one - // channel, we have to multiply the number of available sample "pairs" by numChannels - numSamples *= numChannels; - - const FLAC__int32 *inChannels[MAX_OUTPUT_CHANNELS]; - for (uint i = 0; i < numChannels; ++i) - inChannels[i] = buffer[i]; - - // write the incoming samples directly into the buffer provided to us by the mixer - if (_requestedSamples > 0) { - assert(_requestedSamples % numChannels == 0); - assert(_outBuffer != NULL); - - // Copy & convert the available samples (limited both by how many we have available, and - // by how many are actually needed). - const uint copySamples = MIN(_requestedSamples, numSamples); - (*_methodConvertBuffers)(_outBuffer, inChannels, copySamples, numChannels, numBits); - - _requestedSamples -= copySamples; - numSamples -= copySamples; - _outBuffer += copySamples; - } - - // Write all remaining samples (i.e. those which didn't fit into the mixer buffer) - // into the sample cache. - if (_sampleCache.bufFill == 0) - _sampleCache.bufReadPos = _sampleCache.bufData; - const uint cacheSpace = (_sampleCache.bufData + BUFFER_SIZE) - (_sampleCache.bufReadPos + _sampleCache.bufFill); - assert(numSamples <= cacheSpace); - (*_methodConvertBuffers)(_sampleCache.bufReadPos + _sampleCache.bufFill, inChannels, numSamples, numChannels, numBits); - - _sampleCache.bufFill += numSamples; - - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; -} - -inline ::FLAC__SeekableStreamDecoderSeekStatus FLACStream::callbackSeek(FLAC__uint64 absoluteByteOffset) { - _inStream->seek(absoluteByteOffset, SEEK_SET); - const bool result = (absoluteByteOffset == (FLAC__uint64)_inStream->pos()); - -#ifdef LEGACY_FLAC - return result ? FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK : FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR; -#else - return result ? FLAC__STREAM_DECODER_SEEK_STATUS_OK : FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; -#endif -} - -inline ::FLAC__SeekableStreamDecoderTellStatus FLACStream::callbackTell(FLAC__uint64 *absoluteByteOffset) { - *absoluteByteOffset = static_cast(_inStream->pos()); -#ifdef LEGACY_FLAC - return FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK; -#else - return FLAC__STREAM_DECODER_TELL_STATUS_OK; -#endif -} - -inline ::FLAC__SeekableStreamDecoderLengthStatus FLACStream::callbackLength(FLAC__uint64 *streamLength) { - *streamLength = static_cast(_inStream->size()); -#ifdef LEGACY_FLAC - return FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK; -#else - return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; -#endif -} - -inline bool FLACStream::callbackEOF() { - return _inStream->eos(); -} - - -inline void FLACStream::callbackMetadata(const ::FLAC__StreamMetadata *metadata) { - assert(_decoder != NULL); - assert(metadata->type == FLAC__METADATA_TYPE_STREAMINFO); // others arent really interesting - - _streaminfo = metadata->data.stream_info; - setBestConvertBufferMethod(); // should be set after getting stream-information. FLAC always parses the info first -} -inline void FLACStream::callbackError(::FLAC__StreamDecoderErrorStatus status) { - // some of these are non-critical-Errors - debug(1, "FLACStream: An error occurred while decoding. DecoderState is: %s", - FLAC__StreamDecoderErrorStatusString[status]); -} - -/* Static Callback Wrappers */ -::FLAC__SeekableStreamDecoderReadStatus FLACStream::callWrapRead(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], FLAC_size_t *bytes, void *clientData) { - FLACStream *instance = (FLACStream *)clientData; - assert(0 != instance); - return instance->callbackRead(buffer, bytes); -} - -::FLAC__SeekableStreamDecoderSeekStatus FLACStream::callWrapSeek(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absoluteByteOffset, void *clientData) { - FLACStream *instance = (FLACStream *)clientData; - assert(0 != instance); - return instance->callbackSeek(absoluteByteOffset); -} - -::FLAC__SeekableStreamDecoderTellStatus FLACStream::callWrapTell(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absoluteByteOffset, void *clientData) { - FLACStream *instance = (FLACStream *)clientData; - assert(0 != instance); - return instance->callbackTell(absoluteByteOffset); -} - -::FLAC__SeekableStreamDecoderLengthStatus FLACStream::callWrapLength(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *streamLength, void *clientData) { - FLACStream *instance = (FLACStream *)clientData; - assert(0 != instance); - return instance->callbackLength(streamLength); -} - -FLAC__bool FLACStream::callWrapEOF(const ::FLAC__SeekableStreamDecoder *decoder, void *clientData) { - FLACStream *instance = (FLACStream *)clientData; - assert(0 != instance); - return instance->callbackEOF(); -} - -::FLAC__StreamDecoderWriteStatus FLACStream::callWrapWrite(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *clientData) { - FLACStream *instance = (FLACStream *)clientData; - assert(0 != instance); - return instance->callbackWrite(frame, buffer); -} - -void FLACStream::callWrapMetadata(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__StreamMetadata *metadata, void *clientData) { - FLACStream *instance = (FLACStream *)clientData; - assert(0 != instance); - instance->callbackMetadata(metadata); -} - -void FLACStream::callWrapError(const ::FLAC__SeekableStreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *clientData) { - FLACStream *instance = (FLACStream *)clientData; - assert(0 != instance); - instance->callbackError(status); -} - - -#pragma mark - -#pragma mark --- FLAC factory functions --- -#pragma mark - - -SeekableAudioStream *makeFLACStream( - Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse) { - SeekableAudioStream *s = new FLACStream(stream, disposeAfterUse); - if (s && s->endOfData()) { - delete s; - return 0; - } else { - return s; - } -} - -} // End of namespace Audio - -#endif // #ifdef USE_FLAC diff --git a/sound/decoders/flac.h b/sound/decoders/flac.h deleted file mode 100644 index 17f95ec1fb..0000000000 --- a/sound/decoders/flac.h +++ /dev/null @@ -1,75 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - agos - * - draci - * - kyra - * - m4 - * - queen - * - saga - * - sci - * - scumm - * - sword1 - * - sword2 - * - touche - * - tucker - */ - -#ifndef SOUND_FLAC_H -#define SOUND_FLAC_H - -#include "common/scummsys.h" -#include "common/types.h" - -#ifdef USE_FLAC - -namespace Common { - class SeekableReadStream; -} - -namespace Audio { - -class AudioStream; -class SeekableAudioStream; - -/** - * Create a new SeekableAudioStream from the FLAC data in the given stream. - * Allows for seeking (which is why we require a SeekableReadStream). - * - * @param stream the SeekableReadStream from which to read the FLAC data - * @param disposeAfterUse whether to delete the stream after use - * @return a new SeekableAudioStream, or NULL, if an error occurred - */ -SeekableAudioStream *makeFLACStream( - Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse); - -} // End of namespace Audio - -#endif // #ifdef USE_FLAC -#endif // #ifndef SOUND_FLAC_H diff --git a/sound/decoders/iff_sound.cpp b/sound/decoders/iff_sound.cpp deleted file mode 100644 index 148de5b621..0000000000 --- a/sound/decoders/iff_sound.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/decoders/iff_sound.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" -#include "common/iff_container.h" -#include "common/func.h" - -namespace Audio { - -struct Voice8Header { - uint32 oneShotHiSamples; - uint32 repeatHiSamples; - uint32 samplesPerHiCycle; - uint16 samplesPerSec; - byte octaves; - byte compression; - uint32 volume; - - Voice8Header() { - memset(this, 0, sizeof(Voice8Header)); - } - - void load(Common::ReadStream &stream); -}; - -void Voice8Header::load(Common::ReadStream &stream) { - oneShotHiSamples = stream.readUint32BE(); - repeatHiSamples = stream.readUint32BE(); - samplesPerHiCycle = stream.readUint32BE(); - samplesPerSec = stream.readUint16BE(); - octaves = stream.readByte(); - compression = stream.readByte(); - volume = stream.readUint32BE(); -} - - - -struct A8SVXLoader { - Voice8Header _header; - int8 *_data; - uint32 _dataSize; - - void load(Common::ReadStream &input) { - Common::IFFParser parser(&input); - Common::Functor1Mem< Common::IFFChunk&, bool, A8SVXLoader > c(this, &A8SVXLoader::callback); - parser.parse(c); - } - - bool callback(Common::IFFChunk &chunk) { - switch (chunk._type) { - case ID_VHDR: - _header.load(*chunk._stream); - break; - - case ID_BODY: - _dataSize = chunk._size; - _data = (int8*)malloc(_dataSize); - assert(_data); - loadData(chunk._stream); - return true; - } - - return false; - } - - void loadData(Common::ReadStream *stream) { - switch (_header.compression) { - case 0: - stream->read(_data, _dataSize); - break; - - case 1: - // implement other formats here - error("compressed IFF audio is not supported"); - break; - } - - } -}; - - -AudioStream *make8SVXStream(Common::ReadStream &input, bool loop) { - A8SVXLoader loader; - loader.load(input); - - SeekableAudioStream *stream = Audio::makeRawStream((byte *)loader._data, loader._dataSize, loader._header.samplesPerSec, 0); - - uint32 loopStart = 0, loopEnd = 0; - if (loop) { - // the standard way to loop 8SVX audio implies use of the oneShotHiSamples and - // repeatHiSamples fields - loopStart = 0; - loopEnd = loader._header.oneShotHiSamples + loader._header.repeatHiSamples; - - if (loopStart != loopEnd) { - return new SubLoopingAudioStream(stream, 0, - Timestamp(0, loopStart, loader._header.samplesPerSec), - Timestamp(0, loopEnd, loader._header.samplesPerSec)); - } - } - - return stream; -} - -} diff --git a/sound/decoders/iff_sound.h b/sound/decoders/iff_sound.h deleted file mode 100644 index 4e53059380..0000000000 --- a/sound/decoders/iff_sound.h +++ /dev/null @@ -1,47 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - parallaction - */ - -#ifndef SOUND_IFF_H -#define SOUND_IFF_H - -namespace Common { - class ReadStream; -} - -namespace Audio { - -class AudioStream; - -AudioStream *make8SVXStream(Common::ReadStream &stream, bool loop); - -} - -#endif diff --git a/sound/decoders/mac_snd.cpp b/sound/decoders/mac_snd.cpp deleted file mode 100644 index 48f6886bf4..0000000000 --- a/sound/decoders/mac_snd.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/* - * The code in this file is based on information found at - * http://developer.apple.com/legacy/mac/library/documentation/mac/Sound/Sound-60.html#HEADING60-15 - * - * We implement both type 1 and type 2 snd resources, but only those that are sampled - */ - -#include "common/util.h" -#include "common/stream.h" - -#include "sound/decoders/mac_snd.h" -#include "sound/audiostream.h" -#include "sound/decoders/raw.h" - -namespace Audio { - -SeekableAudioStream *makeMacSndStream(Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse) { - - uint16 sndType = stream->readUint16BE(); - - if (sndType == 1) { - // "normal" snd resources - if (stream->readUint16BE() != 1) { - warning("makeMacSndStream(): Unsupported data type count"); - return 0; - } - - if (stream->readUint16BE() != 5) { - // 5 == sampled - warning("makeMacSndStream(): Unsupported data type"); - return 0; - } - - stream->readUint32BE(); // initialization option - } else if (sndType == 2) { - // old HyperCard snd resources - stream->readUint16BE(); // reference count (unused) - } else { - warning("makeMacSndStream(): Unknown format type %d", sndType); - return 0; - } - - // We really should never get this as long as we have sampled data only - if (stream->readUint16BE() != 1) { - warning("makeMacSndStream(): Unsupported command count"); - return 0; - } - - uint16 command = stream->readUint16BE(); - - // 0x8050 - soundCmd (with dataOffsetFlag set): install a sampled sound as a voice - // 0x8051 - bufferCmd (with dataOffsetFlag set): play a sample sound - if (command != 0x8050 && command != 0x8051) { - warning("makeMacSndStream(): Unsupported command %04x", command); - return 0; - } - - stream->readUint16BE(); // 0 - uint32 soundHeaderOffset = stream->readUint32BE(); - - stream->seek(soundHeaderOffset); - - uint32 soundDataOffset = stream->readUint32BE(); - uint32 size = stream->readUint32BE(); - uint16 rate = stream->readUint32BE() >> 16; // Really fixed point, but we only support integer rates - stream->readUint32BE(); // loop start - stream->readUint32BE(); // loop end - byte encoding = stream->readByte(); - stream->readByte(); // base frequency - - if (encoding != 0) { - // 0 == PCM - warning("makeMacSndStream(): Unsupported compression %d", encoding); - return 0; - } - - stream->skip(soundDataOffset); - - byte *data = (byte *)malloc(size); - assert(data); - stream->read(data, size); - - if (disposeAfterUse == DisposeAfterUse::YES) - delete stream; - - // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. - return makeRawStream(data, size, rate, Audio::FLAG_UNSIGNED); -} - -} // End of namespace Audio diff --git a/sound/decoders/mac_snd.h b/sound/decoders/mac_snd.h deleted file mode 100644 index 198a61333e..0000000000 --- a/sound/decoders/mac_snd.h +++ /dev/null @@ -1,58 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - sci - */ - -#ifndef SOUND_MAC_SND_H -#define SOUND_MAC_SND_H - -#include "common/scummsys.h" -#include "common/types.h" - -namespace Common { class SeekableReadStream; } - -namespace Audio { - -class SeekableAudioStream; - -/** - * Try to load a Mac snd resource from the given seekable stream and create a SeekableAudioStream - * from that data. - * - * @param stream the SeekableReadStream from which to read the snd data - * @param disposeAfterUse whether to delete the stream after use - * @return a new SeekableAudioStream, or NULL, if an error occurred - */ -SeekableAudioStream *makeMacSndStream( - Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse); - -} // End of namespace Audio - -#endif diff --git a/sound/decoders/mp3.cpp b/sound/decoders/mp3.cpp deleted file mode 100644 index e06b82a9e2..0000000000 --- a/sound/decoders/mp3.cpp +++ /dev/null @@ -1,375 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/decoders/mp3.h" - -#ifdef USE_MAD - -#include "common/debug.h" -#include "common/stream.h" -#include "common/util.h" - -#include "sound/audiostream.h" - -#include - -#if defined(__PSP__) - #include "backends/platform/psp/mp3.h" -#endif -namespace Audio { - - -#pragma mark - -#pragma mark --- MP3 (MAD) stream --- -#pragma mark - - - -class MP3Stream : public SeekableAudioStream { -protected: - enum State { - MP3_STATE_INIT, // Need to init the decoder - MP3_STATE_READY, // ready for processing data - MP3_STATE_EOS // end of data reached (may need to loop) - }; - - Common::SeekableReadStream *_inStream; - DisposeAfterUse::Flag _disposeAfterUse; - - uint _posInFrame; - State _state; - - Timestamp _length; - mad_timer_t _totalTime; - - mad_stream _stream; - mad_frame _frame; - mad_synth _synth; - - enum { - BUFFER_SIZE = 5 * 8192 - }; - - // This buffer contains a slab of input data - byte _buf[BUFFER_SIZE + MAD_BUFFER_GUARD]; - -public: - MP3Stream(Common::SeekableReadStream *inStream, - DisposeAfterUse::Flag dispose); - ~MP3Stream(); - - int readBuffer(int16 *buffer, const int numSamples); - - bool endOfData() const { return _state == MP3_STATE_EOS; } - bool isStereo() const { return MAD_NCHANNELS(&_frame.header) == 2; } - int getRate() const { return _frame.header.samplerate; } - - bool seek(const Timestamp &where); - Timestamp getLength() const { return _length; } -protected: - void decodeMP3Data(); - void readMP3Data(); - - void initStream(); - void readHeader(); - void deinitStream(); -}; - -MP3Stream::MP3Stream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) : - _inStream(inStream), - _disposeAfterUse(dispose), - _posInFrame(0), - _state(MP3_STATE_INIT), - _length(0, 1000), - _totalTime(mad_timer_zero) { - - // The MAD_BUFFER_GUARD must always contain zeros (the reason - // for this is that the Layer III Huffman decoder of libMAD - // may read a few bytes beyond the end of the input buffer). - memset(_buf + BUFFER_SIZE, 0, MAD_BUFFER_GUARD); - - // Calculate the length of the stream - initStream(); - - while (_state != MP3_STATE_EOS) - readHeader(); - - // To rule out any invalid sample rate to be encountered here, say in case the - // MP3 stream is invalid, we just check the MAD error code here. - // We need to assure this, since else we might trigger an assertion in Timestamp - // (When getRate() returns 0 or a negative number to be precise). - // Note that we allow "MAD_ERROR_BUFLEN" as error code here, since according - // to mad.h it is also set on EOF. - if ((_stream.error == MAD_ERROR_NONE || _stream.error == MAD_ERROR_BUFLEN) && getRate() > 0) - _length = Timestamp(mad_timer_count(_totalTime, MAD_UNITS_MILLISECONDS), getRate()); - - deinitStream(); - - // Reinit stream - _state = MP3_STATE_INIT; - - // Decode the first chunk of data. This is necessary so that _frame - // is setup and isStereo() and getRate() return correct results. - decodeMP3Data(); -} - -MP3Stream::~MP3Stream() { - deinitStream(); - - if (_disposeAfterUse == DisposeAfterUse::YES) - delete _inStream; -} - -void MP3Stream::decodeMP3Data() { - do { - if (_state == MP3_STATE_INIT) - initStream(); - - if (_state == MP3_STATE_EOS) - return; - - // If necessary, load more data into the stream decoder - if (_stream.error == MAD_ERROR_BUFLEN) - readMP3Data(); - - while (_state == MP3_STATE_READY) { - _stream.error = MAD_ERROR_NONE; - - // Decode the next frame - if (mad_frame_decode(&_frame, &_stream) == -1) { - if (_stream.error == MAD_ERROR_BUFLEN) { - break; // Read more data - } else if (MAD_RECOVERABLE(_stream.error)) { - // Note: we will occasionally see MAD_ERROR_BADDATAPTR errors here. - // These are normal and expected (caused by our frame skipping (i.e. "seeking") - // code above). - debug(6, "MP3Stream: Recoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream)); - continue; - } else { - warning("MP3Stream: Unrecoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream)); - break; - } - } - - // Synthesize PCM data - mad_synth_frame(&_synth, &_frame); - _posInFrame = 0; - break; - } - } while (_state != MP3_STATE_EOS && _stream.error == MAD_ERROR_BUFLEN); - - if (_stream.error != MAD_ERROR_NONE) - _state = MP3_STATE_EOS; -} - -void MP3Stream::readMP3Data() { - uint32 remaining = 0; - - // Give up immediately if we already used up all data in the stream - if (_inStream->eos()) { - _state = MP3_STATE_EOS; - return; - } - - if (_stream.next_frame) { - // If there is still data in the MAD stream, we need to preserve it. - // Note that we use memmove, as we are reusing the same buffer, - // and hence the data regions we copy from and to may overlap. - remaining = _stream.bufend - _stream.next_frame; - assert(remaining < BUFFER_SIZE); // Paranoia check - memmove(_buf, _stream.next_frame, remaining); - } - - // Try to read the next block - uint32 size = _inStream->read(_buf + remaining, BUFFER_SIZE - remaining); - if (size <= 0) { - _state = MP3_STATE_EOS; - return; - } - - // Feed the data we just read into the stream decoder - _stream.error = MAD_ERROR_NONE; - mad_stream_buffer(&_stream, _buf, size + remaining); -} - -bool MP3Stream::seek(const Timestamp &where) { - if (where == _length) { - _state = MP3_STATE_EOS; - return true; - } else if (where > _length) { - return false; - } - - const uint32 time = where.msecs(); - - mad_timer_t destination; - mad_timer_set(&destination, time / 1000, time % 1000, 1000); - - if (_state != MP3_STATE_READY || mad_timer_compare(destination, _totalTime) < 0) - initStream(); - - while (mad_timer_compare(destination, _totalTime) > 0 && _state != MP3_STATE_EOS) - readHeader(); - - decodeMP3Data(); - - return (_state != MP3_STATE_EOS); -} - -void MP3Stream::initStream() { - if (_state != MP3_STATE_INIT) - deinitStream(); - - // Init MAD - mad_stream_init(&_stream); - mad_frame_init(&_frame); - mad_synth_init(&_synth); - - // Reset the stream data - _inStream->seek(0, SEEK_SET); - _totalTime = mad_timer_zero; - _posInFrame = 0; - - // Update state - _state = MP3_STATE_READY; - - // Read the first few sample bytes - readMP3Data(); -} - -void MP3Stream::readHeader() { - if (_state != MP3_STATE_READY) - return; - - // If necessary, load more data into the stream decoder - if (_stream.error == MAD_ERROR_BUFLEN) - readMP3Data(); - - while (_state != MP3_STATE_EOS) { - _stream.error = MAD_ERROR_NONE; - - // Decode the next header. Note: mad_frame_decode would do this for us, too. - // However, for seeking we don't want to decode the full frame (else it would - // be far too slow). Hence we perform this explicitly in a separate step. - if (mad_header_decode(&_frame.header, &_stream) == -1) { - if (_stream.error == MAD_ERROR_BUFLEN) { - readMP3Data(); // Read more data - continue; - } else if (MAD_RECOVERABLE(_stream.error)) { - debug(6, "MP3Stream: Recoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream)); - continue; - } else { - warning("MP3Stream: Unrecoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream)); - break; - } - } - - // Sum up the total playback time so far - mad_timer_add(&_totalTime, _frame.header.duration); - break; - } - - if (_stream.error != MAD_ERROR_NONE) - _state = MP3_STATE_EOS; -} - -void MP3Stream::deinitStream() { - if (_state == MP3_STATE_INIT) - return; - - // Deinit MAD - mad_synth_finish(&_synth); - mad_frame_finish(&_frame); - mad_stream_finish(&_stream); - - _state = MP3_STATE_EOS; -} - -static inline int scale_sample(mad_fixed_t sample) { - // round - sample += (1L << (MAD_F_FRACBITS - 16)); - - // clip - if (sample > MAD_F_ONE - 1) - sample = MAD_F_ONE - 1; - else if (sample < -MAD_F_ONE) - sample = -MAD_F_ONE; - - // quantize and scale to not saturate when mixing a lot of channels - return sample >> (MAD_F_FRACBITS + 1 - 16); -} - -int MP3Stream::readBuffer(int16 *buffer, const int numSamples) { - int samples = 0; - // Keep going as long as we have input available - while (samples < numSamples && _state != MP3_STATE_EOS) { - const int len = MIN(numSamples, samples + (int)(_synth.pcm.length - _posInFrame) * MAD_NCHANNELS(&_frame.header)); - while (samples < len) { - *buffer++ = (int16)scale_sample(_synth.pcm.samples[0][_posInFrame]); - samples++; - if (MAD_NCHANNELS(&_frame.header) == 2) { - *buffer++ = (int16)scale_sample(_synth.pcm.samples[1][_posInFrame]); - samples++; - } - _posInFrame++; - } - if (_posInFrame >= _synth.pcm.length) { - // We used up all PCM data in the current frame -- read & decode more - decodeMP3Data(); - } - } - return samples; -} - - -#pragma mark - -#pragma mark --- MP3 factory functions --- -#pragma mark - - -SeekableAudioStream *makeMP3Stream( - Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse) { - -#if defined(__PSP__) - SeekableAudioStream *s = 0; - - if (Mp3PspStream::isOkToCreateStream()) - s = new Mp3PspStream(stream, disposeAfterUse); - - if (!s) // go to regular MAD mp3 stream if ME fails - s = new MP3Stream(stream, disposeAfterUse); -#else - SeekableAudioStream *s = new MP3Stream(stream, disposeAfterUse); -#endif - if (s && s->endOfData()) { - delete s; - return 0; - } else { - return s; - } -} - -} // End of namespace Audio - -#endif // #ifdef USE_MAD diff --git a/sound/decoders/mp3.h b/sound/decoders/mp3.h deleted file mode 100644 index 72bc6e1b3e..0000000000 --- a/sound/decoders/mp3.h +++ /dev/null @@ -1,76 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - agos - * - draci - * - kyra - * - m4 - * - mohawk - * - queen - * - saga - * - sci - * - scumm - * - sword1 - * - sword2 - * - touche - * - tucker - */ - -#ifndef SOUND_MP3_H -#define SOUND_MP3_H - -#include "common/scummsys.h" -#include "common/types.h" - -#ifdef USE_MAD - -namespace Common { - class SeekableReadStream; -} - -namespace Audio { - -class AudioStream; -class SeekableAudioStream; - -/** - * Create a new SeekableAudioStream from the MP3 data in the given stream. - * Allows for seeking (which is why we require a SeekableReadStream). - * - * @param stream the SeekableReadStream from which to read the MP3 data - * @param disposeAfterUse whether to delete the stream after use - * @return a new SeekableAudioStream, or NULL, if an error occurred - */ -SeekableAudioStream *makeMP3Stream( - Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse); - -} // End of namespace Audio - -#endif // #ifdef USE_MAD -#endif // #ifndef SOUND_MP3_H diff --git a/sound/decoders/raw.cpp b/sound/decoders/raw.cpp deleted file mode 100644 index aedddbf6c4..0000000000 --- a/sound/decoders/raw.cpp +++ /dev/null @@ -1,356 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "common/endian.h" -#include "common/memstream.h" - -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" - -namespace Audio { - -// This used to be an inline template function, but -// buggy template function handling in MSVC6 forced -// us to go with the macro approach. So far this is -// the only template function that MSVC6 seemed to -// compile incorrectly. Knock on wood. -#define READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, ptr, isLE) \ - ((is16Bit ? (isLE ? READ_LE_UINT16(ptr) : READ_BE_UINT16(ptr)) : (*ptr << 8)) ^ (isUnsigned ? 0x8000 : 0)) - - -#pragma mark - -#pragma mark --- RawStream --- -#pragma mark - - -/** - * This is a stream, which allows for playing raw PCM data from a stream. - * It also features playback of multiple blocks from a given stream. - */ -template -class RawStream : public SeekableAudioStream { -public: - RawStream(int rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream, const RawStreamBlockList &blocks) - : _rate(rate), _isStereo(stereo), _playtime(0, rate), _stream(stream), _disposeAfterUse(disposeStream), _blocks(blocks), _curBlock(_blocks.begin()), _blockLeft(0), _buffer(0) { - - assert(_blocks.size() > 0); - - // Setup our buffer for readBuffer - _buffer = new byte[kSampleBufferLength * (is16Bit ? 2 : 1)]; - assert(_buffer); - - // Set current buffer state, playing first block - _stream->seek(_curBlock->pos, SEEK_SET); - - // In case of an error we will stop (or rather - // not start) stream playback. - if (_stream->err()) { - _blockLeft = 0; - _curBlock = _blocks.end(); - } else { - _blockLeft = _curBlock->len; - } - - // Add up length of all blocks in order to caluclate total play time - int32 len = 0; - for (RawStreamBlockList::const_iterator i = _blocks.begin(); i != _blocks.end(); ++i) { - assert(i->len % (_isStereo ? 2 : 1) == 0); - len += i->len; - } - - _playtime = Timestamp(0, len / (_isStereo ? 2 : 1), rate); - } - - ~RawStream() { - if (_disposeAfterUse == DisposeAfterUse::YES) - delete _stream; - - delete[] _buffer; - } - - int readBuffer(int16 *buffer, const int numSamples); - - bool isStereo() const { return _isStereo; } - bool endOfData() const { return (_curBlock == _blocks.end()) && (_blockLeft == 0); } - - int getRate() const { return _rate; } - Timestamp getLength() const { return _playtime; } - - bool seek(const Timestamp &where); -private: - const int _rate; ///< Sample rate of stream - const bool _isStereo; ///< Whether this is an stereo stream - Timestamp _playtime; ///< Calculated total play time - Common::SeekableReadStream *_stream; ///< Stream to read data from - const DisposeAfterUse::Flag _disposeAfterUse; ///< Indicates whether the stream object should be deleted when this RawStream is destructed - const RawStreamBlockList _blocks; ///< Audio block list - - RawStreamBlockList::const_iterator _curBlock; ///< Current audio block number - int32 _blockLeft; ///< How many bytes are still left in the current block - - /** - * Advance one block in the stream in case - * the current one is empty. - */ - void updateBlockIfNeeded(); - - byte *_buffer; ///< Buffer used in readBuffer - enum { - /** - * How many samples we can buffer at once. - * - * TODO: Check whether this size suffices - * for systems with slow disk I/O. - */ - kSampleBufferLength = 2048 - }; - - /** - * Fill the temporary sample buffer used in readBuffer. - * - * @param maxSamples Maximum samples to read. - * @return actual count of samples read. - */ - int fillBuffer(int maxSamples); -}; - -template -int RawStream::readBuffer(int16 *buffer, const int numSamples) { - int samplesLeft = numSamples; - - while (samplesLeft > 0) { - // Try to read up to "samplesLeft" samples. - int len = fillBuffer(samplesLeft); - - // In case we were not able to read any samples - // we will stop reading here. - if (!len) - break; - - // Adjust the samples left to read. - samplesLeft -= len; - - // Copy the data to the caller's buffer. - const byte *src = _buffer; - while (len-- > 0) { - *buffer++ = READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, src, isLE); - src += (is16Bit ? 2 : 1); - } - } - - return numSamples - samplesLeft; -} - -template -int RawStream::fillBuffer(int maxSamples) { - int bufferedSamples = 0; - byte *dst = _buffer; - - // We can only read up to "kSampleBufferLength" samples - // so we take this into consideration, when trying to - // read up to maxSamples. - maxSamples = MIN(kSampleBufferLength, maxSamples); - - // We will only read up to maxSamples - while (maxSamples > 0 && !endOfData()) { - // Calculate how many samples we can safely read - // from the current block. - const int len = MIN(maxSamples, _blockLeft); - - // Try to read all the sample data and update the - // destination pointer. - const int bytesRead = _stream->read(dst, len * (is16Bit ? 2 : 1)); - dst += bytesRead; - - // Calculate how many samples we actually read. - const int samplesRead = bytesRead / (is16Bit ? 2 : 1); - - // Update all status variables - bufferedSamples += samplesRead; - maxSamples -= samplesRead; - _blockLeft -= samplesRead; - - // In case of an error we will stop - // stream playback. - if (_stream->err()) { - _blockLeft = 0; - _curBlock = _blocks.end(); - } - - // Advance to the next block in case the current - // one is already finished. - updateBlockIfNeeded(); - } - - return bufferedSamples; -} - -template -void RawStream::updateBlockIfNeeded() { - // Have we now finished this block? If so, read the next block - if (_blockLeft == 0 && _curBlock != _blocks.end()) { - // Next block - ++_curBlock; - - // Check whether we reached the end of the stream - // yet. In case we did not do this, we will just - // setup the next block as new block. - if (_curBlock != _blocks.end()) { - _stream->seek(_curBlock->pos, SEEK_SET); - - // In case of an error we will stop - // stream playback. - if (_stream->err()) { - _blockLeft = 0; - _curBlock = _blocks.end(); - } else { - _blockLeft = _curBlock->len; - } - } - } -} - -template -bool RawStream::seek(const Timestamp &where) { - _blockLeft = 0; - _curBlock = _blocks.end(); - - if (where > _playtime) - return false; - - const uint32 seekSample = convertTimeToStreamPos(where, getRate(), isStereo()).totalNumberOfFrames(); - uint32 curSample = 0; - - // Search for the disk block in which the specific sample is placed - for (_curBlock = _blocks.begin(); _curBlock != _blocks.end(); ++_curBlock) { - uint32 nextBlockSample = curSample + _curBlock->len; - - if (nextBlockSample > seekSample) - break; - - curSample = nextBlockSample; - } - - if (_curBlock == _blocks.end()) { - return ((seekSample - curSample) == 0); - } else { - const uint32 offset = seekSample - curSample; - - _stream->seek(_curBlock->pos + offset * (is16Bit ? 2 : 1), SEEK_SET); - - // In case of an error we will stop - // stream playback. - if (_stream->err()) { - _blockLeft = 0; - _curBlock = _blocks.end(); - } else { - _blockLeft = _curBlock->len - offset; - } - - return true; - } -} - -#pragma mark - -#pragma mark --- Raw stream factories --- -#pragma mark - - -/* In the following, we use preprocessor / macro tricks to simplify the code - * which instantiates the input streams. We used to use template functions for - * this, but MSVC6 / EVC 3-4 (used for WinCE builds) are extremely buggy when it - * comes to this feature of C++... so as a compromise we use macros to cut down - * on the (source) code duplication a bit. - * So while normally macro tricks are said to make maintenance harder, in this - * particular case it should actually help it :-) - */ - -#define MAKE_RAW_STREAM(UNSIGNED) \ - if (is16Bit) { \ - if (isLE) \ - return new RawStream(rate, isStereo, disposeAfterUse, stream, blockList); \ - else \ - return new RawStream(rate, isStereo, disposeAfterUse, stream, blockList); \ - } else \ - return new RawStream(rate, isStereo, disposeAfterUse, stream, blockList) - -SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream, - const RawStreamBlockList &blockList, - int rate, - byte flags, - DisposeAfterUse::Flag disposeAfterUse) { - const bool isStereo = (flags & Audio::FLAG_STEREO) != 0; - const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0; - const bool isUnsigned = (flags & Audio::FLAG_UNSIGNED) != 0; - const bool isLE = (flags & Audio::FLAG_LITTLE_ENDIAN) != 0; - - if (blockList.empty()) { - warning("Empty block list passed to makeRawStream"); - if (disposeAfterUse == DisposeAfterUse::YES) - delete stream; - return 0; - } - - if (isUnsigned) { - MAKE_RAW_STREAM(true); - } else { - MAKE_RAW_STREAM(false); - } -} - -SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream, - int rate, byte flags, - DisposeAfterUse::Flag disposeAfterUse) { - RawStreamBlockList blocks; - RawStreamBlock block; - block.pos = 0; - - const bool isStereo = (flags & Audio::FLAG_STEREO) != 0; - const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0; - - assert(stream->size() % ((is16Bit ? 2 : 1) * (isStereo ? 2 : 1)) == 0); - - block.len = stream->size() / (is16Bit ? 2 : 1); - blocks.push_back(block); - - return makeRawStream(stream, blocks, rate, flags, disposeAfterUse); -} - -SeekableAudioStream *makeRawStream(const byte *buffer, uint32 size, - int rate, byte flags, - DisposeAfterUse::Flag disposeAfterUse) { - return makeRawStream(new Common::MemoryReadStream(buffer, size, disposeAfterUse), rate, flags, DisposeAfterUse::YES); -} - -SeekableAudioStream *makeRawDiskStream_OLD(Common::SeekableReadStream *stream, RawStreamBlock *block, int numBlocks, - int rate, byte flags, DisposeAfterUse::Flag disposeStream) { - assert(numBlocks > 0); - RawStreamBlockList blocks; - for (int i = 0; i < numBlocks; ++i) - blocks.push_back(block[i]); - - return makeRawStream(stream, blocks, rate, flags, disposeStream); -} - -} // End of namespace Audio diff --git a/sound/decoders/raw.h b/sound/decoders/raw.h deleted file mode 100644 index 3e9426012c..0000000000 --- a/sound/decoders/raw.h +++ /dev/null @@ -1,153 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef SOUND_RAW_H -#define SOUND_RAW_H - -#include "common/scummsys.h" -#include "common/types.h" - -#include "common/list.h" - - -namespace Common { class SeekableReadStream; } - - -namespace Audio { - -class AudioStream; -class SeekableAudioStream; - -/** - * Various flags which can be bit-ORed and then passed to - * makeRawStream and some other AudioStream factories - * to control their behavior. - * - * Engine authors are advised not to rely on a certain value or - * order of these flags (in particular, do not store them verbatim - * in savestates). - */ -enum RawFlags { - /** unsigned samples (default: signed) */ - FLAG_UNSIGNED = 1 << 0, - - /** sound is 16 bits wide (default: 8bit) */ - FLAG_16BITS = 1 << 1, - - /** samples are little endian (default: big endian) */ - FLAG_LITTLE_ENDIAN = 1 << 2, - - /** sound is in stereo (default: mono) */ - FLAG_STEREO = 1 << 3 -}; - - -/** - * Struct used to define the audio data to be played by a RawStream. - */ -struct RawStreamBlock { - int32 pos; ///< Position in stream of the block (in bytes of course!) - int32 len; ///< Length of the block (in raw samples, not sample pairs!) -}; - -/** - * List containing all blocks of a raw stream. - * @see RawStreamBlock - */ -typedef Common::List RawStreamBlockList; - -/** - * Creates an audio stream, which plays from the given buffer. - * - * @param buffer Buffer to play from. - * @param size Size of the buffer in bytes. - * @param rate Rate of the sound data. - * @param flags Audio flags combination. - * @see RawFlags - * @param disposeAfterUse Whether to free the buffer after use (with free!). - * @return The new SeekableAudioStream (or 0 on failure). - */ -SeekableAudioStream *makeRawStream(const byte *buffer, uint32 size, - int rate, byte flags, - DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); - -/** - * Creates an audio stream, which plays from the given stream. - * - * @param stream Stream object to play from. - * @param rate Rate of the sound data. - * @param flags Audio flags combination. - * @see RawFlags - * @param disposeAfterUse Whether to delete the stream after use. - * @return The new SeekableAudioStream (or 0 on failure). - */ -SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream, - int rate, byte flags, - DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); - -/** - * Creates an audio stream, which plays from the given stream. - * - * @param stream Stream object to play from. - * @param blockList List of blocks to play. - * @see RawDiskStreamAudioBlock - * @see RawStreamBlockList - * @param rate Rate of the sound data. - * @param flags Audio flags combination. - * @see RawFlags - * @param disposeAfterUse Whether to delete the stream after use. - * @return The new SeekableAudioStream (or 0 on failure). - */ -SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream, - const RawStreamBlockList &blockList, - int rate, - byte flags, - DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); - -/** - * NOTE: - * This API is considered deprecated. - * - * Creates a audio stream, which plays from given stream. - * - * @param stream Stream to play from - * @param block Pointer to an RawStreamBlock array - * @see RawStreamBlock - * @param numBlocks Number of blocks. - * @param rate The rate - * @param flags Flags combination. - * @see RawFlags - * @param disposeStream Whether the "stream" object should be destroyed after playback. - * @return The new SeekableAudioStream (or 0 on failure). - */ -SeekableAudioStream *makeRawDiskStream_OLD(Common::SeekableReadStream *stream, - RawStreamBlock *block, int numBlocks, - int rate, byte flags, - DisposeAfterUse::Flag disposeStream); - - -} // End of namespace Audio - -#endif diff --git a/sound/decoders/vag.cpp b/sound/decoders/vag.cpp deleted file mode 100644 index d3f0811f2b..0000000000 --- a/sound/decoders/vag.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/decoders/vag.h" -#include "sound/audiostream.h" -#include "common/stream.h" - -namespace Audio { - -class VagStream : public Audio::RewindableAudioStream { -public: - VagStream(Common::SeekableReadStream *stream, int rate); - ~VagStream(); - - bool isStereo() const { return false; } - bool endOfData() const { return _stream->pos() == _stream->size(); } - int getRate() const { return _rate; } - int readBuffer(int16 *buffer, const int numSamples); - - bool rewind(); -private: - Common::SeekableReadStream *_stream; - - byte _predictor; - double _samples[28]; - byte _samplesRemaining; - int _rate; - double _s1, _s2; -}; - -VagStream::VagStream(Common::SeekableReadStream *stream, int rate) : _stream(stream) { - _samplesRemaining = 0; - _predictor = 0; - _s1 = _s2 = 0.0; - _rate = rate; -} - - -VagStream::~VagStream() { - delete _stream; -} - -static const double s_vagDataTable[5][2] = - { - { 0.0, 0.0 }, - { 60.0 / 64.0, 0.0 }, - { 115.0 / 64.0, -52.0 / 64.0 }, - { 98.0 / 64.0, -55.0 / 64.0 }, - { 122.0 / 64.0, -60.0 / 64.0 } - }; - -int VagStream::readBuffer(int16 *buffer, const int numSamples) { - int32 samplesDecoded = 0; - - if (_samplesRemaining) { - byte i = 0; - - for (i = 28 - _samplesRemaining; i < 28 && samplesDecoded < numSamples; i++) { - _samples[i] = _samples[i] + _s1 * s_vagDataTable[_predictor][0] + _s2 * s_vagDataTable[_predictor][1]; - _s2 = _s1; - _s1 = _samples[i]; - int16 d = (int) (_samples[i] + 0.5); - buffer[samplesDecoded] = d; - samplesDecoded++; - } - -#if 0 - assert(i == 28); // We're screwed if this fails :P -#endif - // This might mean the file is corrupted, or that the stream has - // been closed. - if (i != 28) return 0; - - _samplesRemaining = 0; - } - - while (samplesDecoded < numSamples) { - byte i = 0; - - _predictor = _stream->readByte(); - byte shift = _predictor & 0xf; - _predictor >>= 4; - - if (_stream->readByte() == 7) - return samplesDecoded; - - for (i = 0; i < 28; i += 2) { - byte d = _stream->readByte(); - int16 s = (d & 0xf) << 12; - if (s & 0x8000) - s |= 0xffff0000; - _samples[i] = (double)(s >> shift); - s = (d & 0xf0) << 8; - if (s & 0x8000) - s |= 0xffff0000; - _samples[i + 1] = (double)(s >> shift); - } - - for (i = 0; i < 28 && samplesDecoded < numSamples; i++) { - _samples[i] = _samples[i] + _s1 * s_vagDataTable[_predictor][0] + _s2 * s_vagDataTable[_predictor][1]; - _s2 = _s1; - _s1 = _samples[i]; - int16 d = (int) (_samples[i] + 0.5); - buffer[samplesDecoded] = d; - samplesDecoded++; - } - - if (i != 27) - _samplesRemaining = 28 - i; - } - - return samplesDecoded; -} - -bool VagStream::rewind() { - _stream->seek(0); - _samplesRemaining = 0; - _predictor = 0; - _s1 = _s2 = 0.0; - - return true; -} - -RewindableAudioStream *makeVagStream(Common::SeekableReadStream *stream, int rate) { - return new VagStream(stream, rate); -} - -} diff --git a/sound/decoders/vag.h b/sound/decoders/vag.h deleted file mode 100644 index cdf91a8ea1..0000000000 --- a/sound/decoders/vag.h +++ /dev/null @@ -1,60 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - sword1 (PSX port of the game) - * - sword2 (PSX port of the game) - * - tinsel (PSX port of the game) - */ - -#ifndef SOUND_VAG_H -#define SOUND_VAG_H - -namespace Common { - class SeekableReadStream; -} - -namespace Audio { - -class AudioStream; -class RewindableAudioStream; - -/** - * Takes an input stream containing Vag sound data and creates - * an RewindableAudioStream from that. - * - * @param stream the SeekableReadStream from which to read the ADPCM data - * @param rate the sampling rate - * @return a new RewindableAudioStream, or NULL, if an error occurred - */ -RewindableAudioStream *makeVagStream( - Common::SeekableReadStream *stream, - int rate = 11025); - -} // End of namespace Sword1 - -#endif diff --git a/sound/decoders/voc.cpp b/sound/decoders/voc.cpp deleted file mode 100644 index e9af7ece3f..0000000000 --- a/sound/decoders/voc.cpp +++ /dev/null @@ -1,403 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "common/debug.h" -#include "common/endian.h" -#include "common/util.h" -#include "common/stream.h" - -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/voc.h" - - -namespace Audio { - -int getSampleRateFromVOCRate(int vocSR) { - if (vocSR == 0xa5 || vocSR == 0xa6) { - return 11025; - } else if (vocSR == 0xd2 || vocSR == 0xd3) { - return 22050; - } else { - int sr = 1000000L / (256L - vocSR); - // inexact sampling rates occur e.g. in the kitchen in Monkey Island, - // very easy to reach right from the start of the game. - //warning("inexact sample rate used: %i (0x%x)", sr, vocSR); - return sr; - } -} - -static byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate, int &loops, int &begin_loop, int &end_loop) { - VocFileHeader fileHeader; - - debug(2, "loadVOCFromStream"); - - if (stream.read(&fileHeader, 8) != 8) - goto invalid; - - if (!memcmp(&fileHeader, "VTLK", 4)) { - if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader)) - goto invalid; - } else if (!memcmp(&fileHeader, "Creative", 8)) { - if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8) - goto invalid; - } else { - invalid:; - warning("loadVOCFromStream: Invalid header"); - return NULL; - } - - if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0) - error("loadVOCFromStream: Invalid header"); - if (fileHeader.desc[19] != 0x1A) - debug(3, "loadVOCFromStream: Partially invalid header"); - - int32 offset = FROM_LE_16(fileHeader.datablock_offset); - int16 version = FROM_LE_16(fileHeader.version); - int16 code = FROM_LE_16(fileHeader.id); - assert(offset == sizeof(VocFileHeader)); - // 0x100 is an invalid VOC version used by German version of DOTT (Disk) and - // French version of Simon the Sorcerer 2 (CD) - assert(version == 0x010A || version == 0x0114 || version == 0x0100); - assert(code == ~version + 0x1234); - - int len; - byte *ret_sound = 0; - size = 0; - begin_loop = 0; - end_loop = 0; - - while ((code = stream.readByte())) { - len = stream.readByte(); - len |= stream.readByte() << 8; - len |= stream.readByte() << 16; - - debug(2, "Block code %d, len %d", code, len); - - switch (code) { - case 1: - case 9: { - int packing; - if (code == 1) { - int time_constant = stream.readByte(); - packing = stream.readByte(); - len -= 2; - rate = getSampleRateFromVOCRate(time_constant); - } else { - rate = stream.readUint32LE(); - int bits = stream.readByte(); - int channels = stream.readByte(); - if (bits != 8 || channels != 1) { - warning("Unsupported VOC file format (%d bits per sample, %d channels)", bits, channels); - break; - } - packing = stream.readUint16LE(); - stream.readUint32LE(); - len -= 12; - } - debug(9, "VOC Data Block: %d, %d, %d", rate, packing, len); - if (packing == 0) { - if (size) { - ret_sound = (byte *)realloc(ret_sound, size + len); - } else { - ret_sound = (byte *)malloc(len); - } - stream.read(ret_sound + size, len); - size += len; - begin_loop = size; - end_loop = size; - } else { - warning("VOC file packing %d unsupported", packing); - } - } break; - case 3: // silence - // occur with a few Igor sounds, voc file starts with a silence block with a - // frequency different from the data block. Just ignore fow now (implementing - // it wouldn't make a big difference anyway...) - assert(len == 3); - stream.readUint16LE(); - stream.readByte(); - break; - case 6: // begin of loop - assert(len == 2); - loops = stream.readUint16LE(); - break; - case 7: // end of loop - assert(len == 0); - break; - case 8: { // "Extended" - // This occures in the LoL Intro demo. - // This block overwrites the next parameters of a block 1 "Sound data". - // To assure we never get any bad data here, we will assert in case - // this tries to define a stereo sound block or tries to use something - // different than 8bit unsigned sound data. - // TODO: Actually we would need to check the frequency divisor (the - // first word) here too. It is used in the following equation: - // sampleRate = 256000000/(channels * (65536 - frequencyDivisor)) - assert(len == 4); - stream.readUint16LE(); - uint8 codec = stream.readByte(); - uint8 channels = stream.readByte() + 1; - assert(codec == 0 && channels == 1); - } break; - default: - warning("Unhandled code %d in VOC file (len %d)", code, len); - return ret_sound; - } - } - debug(4, "VOC Data Size : %d", size); - return ret_sound; -} - -byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate) { - int loops, begin_loop, end_loop; - return loadVOCFromStream(stream, size, rate, loops, begin_loop, end_loop); -} - - -#ifdef STREAM_AUDIO_FROM_DISK - -int parseVOCFormat(Common::SeekableReadStream& stream, RawStreamBlock* block, int &rate, int &loops, int &begin_loop, int &end_loop) { - VocFileHeader fileHeader; - int currentBlock = 0; - int size = 0; - - debug(2, "parseVOCFormat"); - - if (stream.read(&fileHeader, 8) != 8) - goto invalid; - - if (!memcmp(&fileHeader, "VTLK", 4)) { - if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader)) - goto invalid; - } else if (!memcmp(&fileHeader, "Creative", 8)) { - if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8) - goto invalid; - } else { - invalid:; - warning("loadVOCFromStream: Invalid header"); - return 0; - } - - if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0) - error("loadVOCFromStream: Invalid header"); - if (fileHeader.desc[19] != 0x1A) - debug(3, "loadVOCFromStream: Partially invalid header"); - - int32 offset = FROM_LE_16(fileHeader.datablock_offset); - int16 version = FROM_LE_16(fileHeader.version); - int16 code = FROM_LE_16(fileHeader.id); - assert(offset == sizeof(VocFileHeader)); - // 0x100 is an invalid VOC version used by German version of DOTT (Disk) and - // French version of Simon the Sorcerer 2 (CD) - assert(version == 0x010A || version == 0x0114 || version == 0x0100); - assert(code == ~version + 0x1234); - - int len; - size = 0; - begin_loop = 0; - end_loop = 0; - - while ((code = stream.readByte())) { - len = stream.readByte(); - len |= stream.readByte() << 8; - len |= stream.readByte() << 16; - - debug(2, "Block code %d, len %d", code, len); - - switch (code) { - case 1: - case 9: { - int packing; - if (code == 1) { - int time_constant = stream.readByte(); - packing = stream.readByte(); - len -= 2; - rate = getSampleRateFromVOCRate(time_constant); - } else { - rate = stream.readUint32LE(); - int bits = stream.readByte(); - int channels = stream.readByte(); - if (bits != 8 || channels != 1) { - warning("Unsupported VOC file format (%d bits per sample, %d channels)", bits, channels); - break; - } - packing = stream.readUint16LE(); - stream.readUint32LE(); - len -= 12; - } - debug(9, "VOC Data Block: %d, %d, %d", rate, packing, len); - if (packing == 0) { - - // Found a data block - so add it to the block list - block[currentBlock].pos = stream.pos(); - block[currentBlock].len = len; - currentBlock++; - - stream.seek(len, SEEK_CUR); - - size += len; - begin_loop = size; - end_loop = size; - } else { - warning("VOC file packing %d unsupported", packing); - } - } break; - case 3: // silence - // occur with a few Igor sounds, voc file starts with a silence block with a - // frequency different from the data block. Just ignore fow now (implementing - // it wouldn't make a big difference anyway...) - assert(len == 3); - stream.readUint16LE(); - stream.readByte(); - break; - case 6: // begin of loop - assert(len == 2); - loops = stream.readUint16LE(); - break; - case 7: // end of loop - assert(len == 0); - break; - case 8: // "Extended" - // This occures in the LoL Intro demo. This block can usually be used to create stereo - // sound, but the LoL intro has only an empty block, thus this dummy implementation will - // work. - assert(len == 4); - stream.readUint16LE(); - stream.readByte(); - stream.readByte(); - break; - default: - warning("Unhandled code %d in VOC file (len %d)", code, len); - return 0; - } - } - debug(4, "VOC Data Size : %d", size); - return currentBlock; -} - -AudioStream *makeVOCDiskStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) { - const int MAX_AUDIO_BLOCKS = 256; - - RawStreamBlock *block = new RawStreamBlock[MAX_AUDIO_BLOCKS]; - int rate, loops, begin_loop, end_loop; - - int numBlocks = parseVOCFormat(*stream, block, rate, loops, begin_loop, end_loop); - - AudioStream *audioStream = 0; - - // Create an audiostream from the data. Note the numBlocks may be 0, - // e.g. when invalid data is encountered. See bug #2890038. - if (numBlocks) - audioStream = makeRawDiskStream_OLD(stream, block, numBlocks, rate, flags, disposeAfterUse/*, begin_loop, end_loop*/); - - delete[] block; - - return audioStream; -} - -SeekableAudioStream *makeVOCDiskStreamNoLoop(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) { - const int MAX_AUDIO_BLOCKS = 256; - - RawStreamBlock *block = new RawStreamBlock[MAX_AUDIO_BLOCKS]; - int rate, loops, begin_loop, end_loop; - - int numBlocks = parseVOCFormat(*stream, block, rate, loops, begin_loop, end_loop); - - SeekableAudioStream *audioStream = 0; - - // Create an audiostream from the data. Note the numBlocks may be 0, - // e.g. when invalid data is encountered. See bug #2890038. - if (numBlocks) - audioStream = makeRawDiskStream_OLD(stream, block, numBlocks, rate, flags, disposeAfterUse); - - delete[] block; - - return audioStream; -} - -#endif - - -AudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, uint loopStart, uint loopEnd, DisposeAfterUse::Flag disposeAfterUse) { -#ifdef STREAM_AUDIO_FROM_DISK - return makeVOCDiskStream(stream, flags, disposeAfterUse); -#else - int size, rate; - - byte *data = loadVOCFromStream(*stream, size, rate); - - if (!data) { - if (disposeAfterUse == DisposeAfterUse::YES) - delete stream; - return 0; - } - - SeekableAudioStream *s = Audio::makeRawStream(data, size, rate, flags); - - if (loopStart != loopEnd) { - const bool isStereo = (flags & Audio::FLAG_STEREO) != 0; - const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0; - - if (loopEnd == 0) - loopEnd = size; - assert(loopStart <= loopEnd); - assert(loopEnd <= (uint)size); - - // Verify the buffer sizes are sane - if (is16Bit && isStereo) - assert((loopStart & 3) == 0 && (loopEnd & 3) == 0); - else if (is16Bit || isStereo) - assert((loopStart & 1) == 0 && (loopEnd & 1) == 0); - - const uint32 extRate = s->getRate() * (is16Bit ? 2 : 1) * (isStereo ? 2 : 1); - - return new SubLoopingAudioStream(s, 0, Timestamp(0, loopStart, extRate), Timestamp(0, loopEnd, extRate)); - } else { - return s; - } -#endif -} - -SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) { -#ifdef STREAM_AUDIO_FROM_DISK - return makeVOCDiskStreamNoLoop(stream, flags, disposeAfterUse); -#else - int size, rate; - - byte *data = loadVOCFromStream(*stream, size, rate); - - if (!data) { - if (disposeAfterUse == DisposeAfterUse::YES) - delete stream; - return 0; - } - - return makeRawStream(data, size, rate, flags); -#endif -} - -} // End of namespace Audio diff --git a/sound/decoders/voc.h b/sound/decoders/voc.h deleted file mode 100644 index 82cc261f2c..0000000000 --- a/sound/decoders/voc.h +++ /dev/null @@ -1,107 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - agos - * - drascula - * - kyra - * - made - * - saga - * - scumm - * - touche - */ - -#ifndef SOUND_VOC_H -#define SOUND_VOC_H - -#include "common/scummsys.h" -#include "common/types.h" - -namespace Common { class ReadStream; } -namespace Common { class SeekableReadStream; } - -namespace Audio { - -class AudioStream; -class SeekableAudioStream; - - -#include "common/pack-start.h" // START STRUCT PACKING - -struct VocFileHeader { - uint8 desc[20]; - uint16 datablock_offset; - uint16 version; - uint16 id; -} PACKED_STRUCT; - -struct VocBlockHeader { - uint8 blocktype; - uint8 size[3]; - uint8 sr; - uint8 pack; -} PACKED_STRUCT; - -#include "common/pack-end.h" // END STRUCT PACKING - -/** - * Take a sample rate parameter as it occurs in a VOC sound header, and - * return the corresponding sample frequency. - * - * This method has special cases for the standard rates of 11025 and 22050 kHz, - * which due to limitations of the format, cannot be encoded exactly in a VOC - * file. As a consequence, many game files have sound data sampled with those - * rates, but the VOC marks them incorrectly as 11111 or 22222 kHz. This code - * works around that and "unrounds" the sampling rates. - */ -extern int getSampleRateFromVOCRate(int vocSR); - -/** - * Try to load a VOC from the given stream. Returns a pointer to memory - * containing the PCM sample data (allocated with malloc). It is the callers - * responsibility to dellocate that data again later on! Currently this - * function only supports uncompressed raw PCM data. - */ -extern byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate); - -/** - * Try to load a VOC from the given seekable stream and create an AudioStream - * from that data. Currently this function only supports uncompressed raw PCM - * data. Optionally supports (infinite) looping of a portion of the data. - * - * This function uses loadVOCFromStream() internally. - */ -AudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags = 0, uint loopStart = 0, uint loopEnd = 0, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::NO); - -/** - * This does not use any of the looping features of VOC files! - */ -SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse); - -} // End of namespace Audio - -#endif diff --git a/sound/decoders/vorbis.cpp b/sound/decoders/vorbis.cpp deleted file mode 100644 index 425eb6b751..0000000000 --- a/sound/decoders/vorbis.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -// Disable symbol overrides for FILE and fseek as those are used in the -// Vorbis headers. -#define FORBIDDEN_SYMBOL_EXCEPTION_FILE -#define FORBIDDEN_SYMBOL_EXCEPTION_fseek - -#include "sound/decoders/vorbis.h" - -#ifdef USE_VORBIS - -#include "common/debug.h" -#include "common/stream.h" -#include "common/util.h" - -#include "sound/audiostream.h" - -#ifdef USE_TREMOR -#if defined(__GP32__) // custom libtremor locations -#include -#else -#include -#endif -#else -#include -#endif - - -namespace Audio { - -// These are wrapper functions to allow using a SeekableReadStream object to -// provide data to the OggVorbis_File object. - -static size_t read_stream_wrap(void *ptr, size_t size, size_t nmemb, void *datasource) { - Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource; - - uint32 result = stream->read(ptr, size * nmemb); - - return result / size; -} - -static int seek_stream_wrap(void *datasource, ogg_int64_t offset, int whence) { - Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource; - stream->seek((int32)offset, whence); - return stream->pos(); -} - -static int close_stream_wrap(void *datasource) { - // Do nothing -- we leave it up to the VorbisStream to free memory as appropriate. - return 0; -} - -static long tell_stream_wrap(void *datasource) { - Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource; - return stream->pos(); -} - -static ov_callbacks g_stream_wrap = { - read_stream_wrap, seek_stream_wrap, close_stream_wrap, tell_stream_wrap -}; - - - -#pragma mark - -#pragma mark --- Ogg Vorbis stream --- -#pragma mark - - - -class VorbisStream : public SeekableAudioStream { -protected: - Common::SeekableReadStream *_inStream; - DisposeAfterUse::Flag _disposeAfterUse; - - bool _isStereo; - int _rate; - - Timestamp _length; - - OggVorbis_File _ovFile; - - int16 _buffer[4096]; - const int16 *_bufferEnd; - const int16 *_pos; - -public: - // startTime / duration are in milliseconds - VorbisStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose); - ~VorbisStream(); - - int readBuffer(int16 *buffer, const int numSamples); - - bool endOfData() const { return _pos >= _bufferEnd; } - bool isStereo() const { return _isStereo; } - int getRate() const { return _rate; } - - bool seek(const Timestamp &where); - Timestamp getLength() const { return _length; } -protected: - bool refill(); -}; - -VorbisStream::VorbisStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) : - _inStream(inStream), - _disposeAfterUse(dispose), - _length(0, 1000), - _bufferEnd(_buffer + ARRAYSIZE(_buffer)) { - - int res = ov_open_callbacks(inStream, &_ovFile, NULL, 0, g_stream_wrap); - if (res < 0) { - warning("Could not create Vorbis stream (%d)", res); - _pos = _bufferEnd; - return; - } - - // Read in initial data - if (!refill()) - return; - - // Setup some header information - _isStereo = ov_info(&_ovFile, -1)->channels >= 2; - _rate = ov_info(&_ovFile, -1)->rate; - -#ifdef USE_TREMOR - _length = Timestamp(ov_time_total(&_ovFile, -1), getRate()); -#else - _length = Timestamp(uint32(ov_time_total(&_ovFile, -1) * 1000.0), getRate()); -#endif -} - -VorbisStream::~VorbisStream() { - ov_clear(&_ovFile); - if (_disposeAfterUse == DisposeAfterUse::YES) - delete _inStream; -} - -int VorbisStream::readBuffer(int16 *buffer, const int numSamples) { - int samples = 0; - while (samples < numSamples && _pos < _bufferEnd) { - const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos)); - memcpy(buffer, _pos, len * 2); - buffer += len; - _pos += len; - samples += len; - if (_pos >= _bufferEnd) { - if (!refill()) - break; - } - } - return samples; -} - -bool VorbisStream::seek(const Timestamp &where) { - // Vorbisfile uses the sample pair number, thus we always use "false" for the isStereo parameter - // of the convertTimeToStreamPos helper. - int res = ov_pcm_seek(&_ovFile, convertTimeToStreamPos(where, getRate(), false).totalNumberOfFrames()); - if (res) { - warning("Error seeking in Vorbis stream (%d)", res); - _pos = _bufferEnd; - return false; - } - - return refill(); -} - -bool VorbisStream::refill() { - // Read the samples - uint len_left = sizeof(_buffer); - char *read_pos = (char *)_buffer; - - while (len_left > 0) { - long result; - -#ifdef USE_TREMOR - // Tremor ov_read() always returns data as signed 16 bit interleaved PCM - // in host byte order. As such, it does not take arguments to request - // specific signedness, byte order or bit depth as in Vorbisfile. - result = ov_read(&_ovFile, read_pos, len_left, - NULL); -#else -#ifdef SCUMM_BIG_ENDIAN - result = ov_read(&_ovFile, read_pos, len_left, - 1, - 2, // 16 bit - 1, // signed - NULL); -#else - result = ov_read(&_ovFile, read_pos, len_left, - 0, - 2, // 16 bit - 1, // signed - NULL); -#endif -#endif - if (result == OV_HOLE) { - // Possibly recoverable, just warn about it - warning("Corrupted data in Vorbis file"); - } else if (result == 0) { - //warning("End of file while reading from Vorbis file"); - //_pos = _bufferEnd; - //return false; - break; - } else if (result < 0) { - warning("Error reading from Vorbis stream (%d)", int(result)); - _pos = _bufferEnd; - // Don't delete it yet, that causes problems in - // the CD player emulation code. - return false; - } else { - len_left -= result; - read_pos += result; - } - } - - _pos = _buffer; - _bufferEnd = (int16 *)read_pos; - - return true; -} - - -#pragma mark - -#pragma mark --- Ogg Vorbis factory functions --- -#pragma mark - - -SeekableAudioStream *makeVorbisStream( - Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse) { - SeekableAudioStream *s = new VorbisStream(stream, disposeAfterUse); - if (s && s->endOfData()) { - delete s; - return 0; - } else { - return s; - } -} - -} // End of namespace Audio - -#endif // #ifdef USE_VORBIS diff --git a/sound/decoders/vorbis.h b/sound/decoders/vorbis.h deleted file mode 100644 index 7cc395cccb..0000000000 --- a/sound/decoders/vorbis.h +++ /dev/null @@ -1,75 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - agos - * - draci - * - kyra - * - m4 - * - queen - * - saga - * - sci - * - scumm - * - sword1 - * - sword2 - * - touche - * - tucker - */ - -#ifndef SOUND_VORBIS_H -#define SOUND_VORBIS_H - -#include "common/scummsys.h" -#include "common/types.h" - -#ifdef USE_VORBIS - -namespace Common { - class SeekableReadStream; -} - -namespace Audio { - -class AudioStream; -class SeekableAudioStream; - -/** - * Create a new SeekableAudioStream from the Ogg Vorbis data in the given stream. - * Allows for seeking (which is why we require a SeekableReadStream). - * - * @param stream the SeekableReadStream from which to read the Ogg Vorbis data - * @param disposeAfterUse whether to delete the stream after use - * @return a new SeekableAudioStream, or NULL, if an error occurred - */ -SeekableAudioStream *makeVorbisStream( - Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse); - -} // End of namespace Audio - -#endif // #ifdef USE_VORBIS -#endif // #ifndef SOUND_VORBIS_H diff --git a/sound/decoders/wave.cpp b/sound/decoders/wave.cpp deleted file mode 100644 index fcaace5301..0000000000 --- a/sound/decoders/wave.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "common/debug.h" -#include "common/util.h" -#include "common/stream.h" - -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/wave.h" -#include "sound/decoders/adpcm.h" -#include "sound/decoders/raw.h" - -namespace Audio { - -bool loadWAVFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags, uint16 *wavType, int *blockAlign_) { - const int32 initialPos = stream.pos(); - byte buf[4+1]; - - buf[4] = 0; - - stream.read(buf, 4); - if (memcmp(buf, "RIFF", 4) != 0) { - warning("getWavInfo: No 'RIFF' header"); - return false; - } - - int32 wavLength = stream.readUint32LE(); - - stream.read(buf, 4); - if (memcmp(buf, "WAVE", 4) != 0) { - warning("getWavInfo: No 'WAVE' header"); - return false; - } - - stream.read(buf, 4); - if (memcmp(buf, "fmt ", 4) != 0) { - warning("getWavInfo: No 'fmt' header"); - return false; - } - - uint32 fmtLength = stream.readUint32LE(); - if (fmtLength < 16) { - // A valid fmt chunk always contains at least 16 bytes - warning("getWavInfo: 'fmt' header is too short"); - return false; - } - - // Next comes the "type" field of the fmt header. Some typical - // values for it: - // 1 -> uncompressed PCM - // 17 -> IMA ADPCM compressed WAVE - // See for a more complete - // list of common WAVE compression formats... - uint16 type = stream.readUint16LE(); // == 1 for PCM data - uint16 numChannels = stream.readUint16LE(); // 1 for mono, 2 for stereo - uint32 samplesPerSec = stream.readUint32LE(); // in Hz - uint32 avgBytesPerSec = stream.readUint32LE(); // == SampleRate * NumChannels * BitsPerSample/8 - - uint16 blockAlign = stream.readUint16LE(); // == NumChannels * BitsPerSample/8 - uint16 bitsPerSample = stream.readUint16LE(); // 8, 16 ... - // 8 bit data is unsigned, 16 bit data signed - - - if (wavType != 0) - *wavType = type; - - if (blockAlign_ != 0) - *blockAlign_ = blockAlign; -#if 0 - debug("WAVE information:"); - debug(" total size: %d", wavLength); - debug(" fmt size: %d", fmtLength); - debug(" type: %d", type); - debug(" numChannels: %d", numChannels); - debug(" samplesPerSec: %d", samplesPerSec); - debug(" avgBytesPerSec: %d", avgBytesPerSec); - debug(" blockAlign: %d", blockAlign); - debug(" bitsPerSample: %d", bitsPerSample); -#endif - - if (type != 1 && type != 2 && type != 17) { - warning("getWavInfo: only PCM, MS ADPCM or IMA ADPCM data is supported (type %d)", type); - return false; - } - - if (blockAlign != numChannels * bitsPerSample / 8 && type != 2) { - debug(0, "getWavInfo: blockAlign is invalid"); - } - - if (avgBytesPerSec != samplesPerSec * blockAlign && type != 2) { - debug(0, "getWavInfo: avgBytesPerSec is invalid"); - } - - // Prepare the return values. - rate = samplesPerSec; - - flags = 0; - if (bitsPerSample == 8) // 8 bit data is unsigned - flags |= Audio::FLAG_UNSIGNED; - else if (bitsPerSample == 16) // 16 bit data is signed little endian - flags |= (Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN); - else if (bitsPerSample == 4 && (type == 2 || type == 17)) - flags |= Audio::FLAG_16BITS; - else { - warning("getWavInfo: unsupported bitsPerSample %d", bitsPerSample); - return false; - } - - if (numChannels == 2) - flags |= Audio::FLAG_STEREO; - else if (numChannels != 1) { - warning("getWavInfo: unsupported number of channels %d", numChannels); - return false; - } - - // It's almost certainly a WAV file, but we still need to find its - // 'data' chunk. - - // Skip over the rest of the fmt chunk. - int offset = fmtLength - 16; - - do { - stream.seek(offset, SEEK_CUR); - if (stream.pos() >= initialPos + wavLength + 8) { - warning("getWavInfo: Can't find 'data' chunk"); - return false; - } - stream.read(buf, 4); - offset = stream.readUint32LE(); - -#if 0 - debug(" found a '%s' tag of size %d", buf, offset); -#endif - } while (memcmp(buf, "data", 4) != 0); - - // Stream now points at 'offset' bytes of sample data... - size = offset; - - return true; -} - -RewindableAudioStream *makeWAVStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) { - int size, rate; - byte flags; - uint16 type; - int blockAlign; - - if (!loadWAVFromStream(*stream, size, rate, flags, &type, &blockAlign)) { - if (disposeAfterUse == DisposeAfterUse::YES) - delete stream; - return 0; - } - - if (type == 17) // MS IMA ADPCM - return makeADPCMStream(stream, disposeAfterUse, size, Audio::kADPCMMSIma, rate, (flags & Audio::FLAG_STEREO) ? 2 : 1, blockAlign); - else if (type == 2) // MS ADPCM - return makeADPCMStream(stream, disposeAfterUse, size, Audio::kADPCMMS, rate, (flags & Audio::FLAG_STEREO) ? 2 : 1, blockAlign); - - // Raw PCM. Just read everything at once. - // TODO: More elegant would be to wrap the stream. - byte *data = (byte *)malloc(size); - assert(data); - stream->read(data, size); - - if (disposeAfterUse == DisposeAfterUse::YES) - delete stream; - - return makeRawStream(data, size, rate, flags); -} - -} // End of namespace Audio diff --git a/sound/decoders/wave.h b/sound/decoders/wave.h deleted file mode 100644 index 2bdbe8f0b6..0000000000 --- a/sound/decoders/wave.h +++ /dev/null @@ -1,84 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - agos - * - gob - * - mohawk - * - saga - * - sci - * - scumm - * - sword1 - * - sword2 - * - tucker - */ - -#ifndef SOUND_WAVE_H -#define SOUND_WAVE_H - -#include "common/scummsys.h" -#include "common/types.h" - -namespace Common { class SeekableReadStream; } - -namespace Audio { - -class RewindableAudioStream; - -/** - * Try to load a WAVE from the given seekable stream. Returns true if - * successful. In that case, the stream's seek position will be set to the - * start of the audio data, and size, rate and flags contain information - * necessary for playback. Currently this function supports uncompressed - * raw PCM data, MS IMA ADPCM and MS ADPCM (uses makeADPCMStream internally). - */ -extern bool loadWAVFromStream( - Common::SeekableReadStream &stream, - int &size, - int &rate, - byte &flags, - uint16 *wavType = 0, - int *blockAlign = 0); - -/** - * Try to load a WAVE from the given seekable stream and create an AudioStream - * from that data. Currently this function supports uncompressed - * raw PCM data, MS IMA ADPCM and MS ADPCM (uses makeADPCMStream internally). - * - * This function uses loadWAVFromStream() internally. - * - * @param stream the SeekableReadStream from which to read the WAVE data - * @param disposeAfterUse whether to delete the stream after use - * @return a new RewindableAudioStream, or NULL, if an error occurred - */ -RewindableAudioStream *makeWAVStream( - Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse); - -} // End of namespace Audio - -#endif diff --git a/sound/fmopl.cpp b/sound/fmopl.cpp deleted file mode 100644 index ee54a79a46..0000000000 --- a/sound/fmopl.cpp +++ /dev/null @@ -1,197 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#include "sound/fmopl.h" - -#include "sound/softsynth/opl/dosbox.h" -#include "sound/softsynth/opl/mame.h" - -#include "common/config-manager.h" -#include "common/translation.h" - -namespace OPL { - -// Config implementation - -enum OplEmulator { - kAuto = 0, - kMame = 1, - kDOSBox = 2 -}; - -OPL::OPL() { - if (_hasInstance) - error("There are multiple OPL output instances running"); - _hasInstance = true; -} - -const Config::EmulatorDescription Config::_drivers[] = { - { "auto", "", kAuto, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 }, - { "mame", _s("MAME OPL emulator"), kMame, kFlagOpl2 }, -#ifndef DISABLE_DOSBOX_OPL - { "db", _s("DOSBox OPL emulator"), kDOSBox, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 }, -#endif - { 0, 0, 0, 0 } -}; - -Config::DriverId Config::parse(const Common::String &name) { - for (int i = 0; _drivers[i].name; ++i) { - if (name.equalsIgnoreCase(_drivers[i].name)) - return _drivers[i].id; - } - - return -1; -} - -Config::DriverId Config::detect(OplType type) { - uint32 flags = 0; - switch (type) { - case kOpl2: - flags = kFlagOpl2; - break; - - case kDualOpl2: - flags = kFlagDualOpl2; - break; - - case kOpl3: - flags = kFlagOpl3; - break; - } - - DriverId drv = parse(ConfMan.get("opl_driver")); - - // When a valid driver is selected, check whether it supports - // the requested OPL chip. - if (drv != -1 && drv != kAuto) { - // If the chip is supported, just use the driver. - if ((flags & _drivers[drv].flags)) { - return drv; - } else { - // Else we will output a warning and just - // return that no valid driver is found. - warning("Your selected OPL driver \"%s\" does not support type %d emulation, which is requested by your game", _drivers[drv].description, type); - return -1; - } - } - - // Detect the first matching emulator - drv = -1; - - for (int i = 1; _drivers[i].name; ++i) { - if (_drivers[i].flags & flags) { - drv = _drivers[i].id; - break; - } - } - - return drv; -} - -OPL *Config::create(OplType type) { - return create(kAuto, type); -} - -OPL *Config::create(DriverId driver, OplType type) { - // On invalid driver selection, we try to do some fallback detection - if (driver == -1) { - warning("Invalid OPL driver selected, trying to detect a fallback emulator"); - driver = kAuto; - } - - // If autodetection is selected, we search for a matching - // driver. - if (driver == kAuto) { - driver = detect(type); - - // No emulator for the specified OPL chip could - // be found, thus stop here. - if (driver == -1) { - warning("No OPL emulator available for type %d", type); - return 0; - } - } - - switch (driver) { - case kMame: - if (type == kOpl2) - return new MAME::OPL(); - else - warning("MAME OPL emulator only supports OPL2 emulation"); - return 0; - -#ifndef DISABLE_DOSBOX_OPL - case kDOSBox: - return new DOSBox::OPL(type); -#endif - - default: - warning("Unsupported OPL emulator %d", driver); - // TODO: Maybe we should add some dummy emulator too, which just outputs - // silence as sound? - return 0; - } -} - -bool OPL::_hasInstance = false; - -} // End of namespace OPL - -void OPLDestroy(FM_OPL *OPL) { - delete OPL; -} - -void OPLResetChip(FM_OPL *OPL) { - OPL->reset(); -} - -void OPLWrite(FM_OPL *OPL, int a, int v) { - OPL->write(a, v); -} - -unsigned char OPLRead(FM_OPL *OPL, int a) { - return OPL->read(a); -} - -void OPLWriteReg(FM_OPL *OPL, int r, int v) { - OPL->writeReg(r, v); -} - -void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length) { - OPL->readBuffer(buffer, length); -} - -FM_OPL *makeAdLibOPL(int rate) { - FM_OPL *opl = OPL::Config::create(); - - if (opl) { - if (!opl->init(rate)) { - delete opl; - opl = 0; - } - } - - return opl; -} - diff --git a/sound/fmopl.h b/sound/fmopl.h deleted file mode 100644 index 33235f3545..0000000000 --- a/sound/fmopl.h +++ /dev/null @@ -1,179 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#ifndef SOUND_FMOPL_H -#define SOUND_FMOPL_H - -#include "common/scummsys.h" -#include "common/str.h" - -namespace OPL { - -class OPL; - -class Config { -public: - enum OplFlags { - kFlagOpl2 = (1 << 0), - kFlagDualOpl2 = (1 << 1), - kFlagOpl3 = (1 << 2) - }; - - /** - * OPL type to emulate. - */ - enum OplType { - kOpl2, - kDualOpl2, - kOpl3 - }; - - typedef int8 DriverId; - struct EmulatorDescription { - const char *name; - const char *description; - - DriverId id; // A unique ID for each driver - uint32 flags; // Capabilities of this driver - }; - - /** - * Get a list of all available OPL emulators. - * @return list of all available OPL emulators, terminated by a zero entry - */ - static const EmulatorDescription *getAvailable() { return _drivers; } - - /** - * Returns the driver id of a given name. - */ - static DriverId parse(const Common::String &name); - - /** - * Detects a driver for the specific type. - * - * @return Returns a valid driver id on success, -1 otherwise. - */ - static DriverId detect(OplType type); - - /** - * Creates the specific driver with a specific type setup. - */ - static OPL *create(DriverId driver, OplType type); - - /** - * Wrapper to easily init an OPL chip, without specifing an emulator. - * By default it will try to initialize an OPL2 emulator, thus an AdLib card. - */ - static OPL *create(OplType type = kOpl2); - -private: - static const EmulatorDescription _drivers[]; -}; - -class OPL { -private: - static bool _hasInstance; -public: - OPL(); - virtual ~OPL() { _hasInstance = false; } - - /** - * Initializes the OPL emulator. - * - * @param rate output sample rate - * @return true on success, false on failure - */ - virtual bool init(int rate) = 0; - - /** - * Reinitializes the OPL emulator - */ - virtual void reset() = 0; - - /** - * Writes a byte to the given I/O port. - * - * @param a port address - * @param v value, which will be written - */ - virtual void write(int a, int v) = 0; - - /** - * Reads a byte from the given I/O port. - * - * @param a port address - * @return value read - */ - virtual byte read(int a) = 0; - - /** - * Function to directly write to a specific OPL register. - * This writes to *both* chips for a Dual OPL2. - * - * @param r hardware register number to write to - * @param v value, which will be written - */ - virtual void writeReg(int r, int v) = 0; - - /** - * Read up to 'length' samples. - * - * Data will be in native endianess, 16 bit per sample, signed. - * For stereo OPL, buffer will be filled with interleaved - * left and right channel samples, starting with a left sample. - * Furthermore, the samples in the left and right are summed up. - * So if you request 4 samples from a stereo OPL, you will get - * a total of two left channel and two right channel samples. - */ - virtual void readBuffer(int16 *buffer, int length) = 0; - - /** - * Returns whether the setup OPL mode is stereo or not - */ - virtual bool isStereo() const = 0; -}; - -} // End of namespace OPL - -// Legacy API -// !You should not write any new code using the legacy API! -typedef OPL::OPL FM_OPL; - -void OPLDestroy(FM_OPL *OPL); - -void OPLResetChip(FM_OPL *OPL); -void OPLWrite(FM_OPL *OPL, int a, int v); -unsigned char OPLRead(FM_OPL *OPL, int a); -void OPLWriteReg(FM_OPL *OPL, int r, int v); -void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length); - -/** - * Legacy factory to create an AdLib (OPL2) chip. - * - * !You should not write any new code using the legacy API! - */ -FM_OPL *makeAdLibOPL(int rate); - -#endif - diff --git a/sound/mididrv.cpp b/sound/mididrv.cpp deleted file mode 100644 index 20d5a4e233..0000000000 --- a/sound/mididrv.cpp +++ /dev/null @@ -1,326 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "engines/engine.h" -#include "common/config-manager.h" -#include "common/str.h" -#include "common/system.h" -#include "common/util.h" -#include "sound/mididrv.h" -#include "sound/musicplugin.h" -#include "common/translation.h" - -const byte MidiDriver::_mt32ToGm[128] = { -// 0 1 2 3 4 5 6 7 8 9 A B C D E F - 0, 1, 0, 2, 4, 4, 5, 3, 16, 17, 18, 16, 16, 19, 20, 21, // 0x - 6, 6, 6, 7, 7, 7, 8, 112, 62, 62, 63, 63, 38, 38, 39, 39, // 1x - 88, 95, 52, 98, 97, 99, 14, 54, 102, 96, 53, 102, 81, 100, 14, 80, // 2x - 48, 48, 49, 45, 41, 40, 42, 42, 43, 46, 45, 24, 25, 28, 27, 104, // 3x - 32, 32, 34, 33, 36, 37, 35, 35, 79, 73, 72, 72, 74, 75, 64, 65, // 4x - 66, 67, 71, 71, 68, 69, 70, 22, 56, 59, 57, 57, 60, 60, 58, 61, // 5x - 61, 11, 11, 98, 14, 9, 14, 13, 12, 107, 107, 77, 78, 78, 76, 76, // 6x - 47, 117, 127, 118, 118, 116, 115, 119, 115, 112, 55, 124, 123, 0, 14, 117 // 7x -}; - -const byte MidiDriver::_gmToMt32[128] = { -// 0 1 2 3 4 5 6 7 8 9 A B C D E F - 5, 1, 2, 7, 3, 5, 16, 21, 22, 101, 101, 97, 104, 103, 102, 20, // 0x - 8, 9, 11, 12, 14, 15, 87, 15, 59, 60, 61, 62, 67, 44, 79, 23, // 1x - 64, 67, 66, 70, 68, 69, 28, 31, 52, 54, 55, 56, 49, 51, 57, 112, // 2x - 48, 50, 45, 26, 34, 35, 45, 122, 89, 90, 94, 81, 92, 95, 24, 25, // 3x - 80, 78, 79, 78, 84, 85, 86, 82, 74, 72, 76, 77, 110, 107, 108, 76, // 4x - 47, 44, 111, 45, 44, 34, 44, 30, 32, 33, 88, 34, 35, 35, 38, 33, // 5x - 41, 36, 100, 37, 40, 34, 43, 40, 63, 21, 99, 105, 103, 86, 55, 84, // 6x - 101, 103, 100, 120, 117, 113, 99, 128, 128, 128, 128, 124, 123, 128, 128, 128, // 7x -}; - -static const uint32 GUIOMapping[] = { - MT_PCSPK, Common::GUIO_MIDIPCSPK, - MT_CMS, Common::GUIO_MIDICMS, - MT_PCJR, Common::GUIO_MIDIPCJR, - MT_ADLIB, Common::GUIO_MIDIADLIB, - MT_C64, Common::GUIO_MIDIC64, - MT_AMIGA, Common::GUIO_MIDIAMIGA, - MT_APPLEIIGS, Common::GUIO_MIDIAPPLEIIGS, - MT_TOWNS, Common::GUIO_MIDITOWNS, - MT_PC98, Common::GUIO_MIDIPC98, - MT_GM, Common::GUIO_MIDIGM, - MT_MT32, Common::GUIO_MIDIMT32, - 0, 0 -}; - -uint32 MidiDriver::musicType2GUIO(uint32 musicType) { - uint32 res = 0; - - for (int i = 0; GUIOMapping[i] || GUIOMapping[i + 1]; i += 2) { - if (musicType == GUIOMapping[i] || musicType == (uint32)-1) - res |= GUIOMapping[i + 1]; - } - - return res; -} - -bool MidiDriver::_forceTypeMT32 = false; - -MusicType MidiDriver::getMusicType(MidiDriver::DeviceHandle handle) { - if (_forceTypeMT32) - return MT_MT32; - - if (handle) { - const MusicPlugin::List p = MusicMan.getPlugins(); - for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); m++) { - MusicDevices i = (**m)->getDevices(); - for (MusicDevices::iterator d = i.begin(); d != i.end(); d++) { - if (handle == d->getHandle()) - return d->getMusicType(); - } - } - } - - return MT_INVALID; -} - -Common::String MidiDriver::getDeviceString(DeviceHandle handle, DeviceStringType type) { - if (handle) { - const MusicPlugin::List p = MusicMan.getPlugins(); - for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); m++) { - MusicDevices i = (**m)->getDevices(); - for (MusicDevices::iterator d = i.begin(); d != i.end(); d++) { - if (handle == d->getHandle()) { - if (type == kDriverName) - return d->getMusicDriverName(); - else if (type == kDriverId) - return d->getMusicDriverId(); - else if (type == kDeviceId) - return d->getCompleteId(); - else - return Common::String("auto"); - } - } - } - } - - return Common::String("auto"); -} - -MidiDriver::DeviceHandle MidiDriver::detectDevice(int flags) { - // Query the selected music device (defaults to MT_AUTO device). - DeviceHandle hdl = getDeviceHandle(ConfMan.get("music_driver")); - - _forceTypeMT32 = false; - - // Check whether the selected music driver is compatible with the - // given flags. - switch (getMusicType(hdl)) { - case MT_PCSPK: - if (flags & MDT_PCSPK) - return hdl; - break; - - case MT_PCJR: - if (flags & MDT_PCJR) - return hdl; - break; - - case MT_CMS: - if (flags & MDT_CMS) - return hdl; - break; - - case MT_ADLIB: - if (flags & MDT_ADLIB) - return hdl; - break; - - case MT_C64: - if (flags & MDT_C64) - return hdl; - break; - - case MT_AMIGA: - if (flags & MDT_AMIGA) - return hdl; - break; - - case MT_APPLEIIGS: - if (flags & MDT_APPLEIIGS) - return hdl; - break; - - case MT_TOWNS: - if (flags & MDT_TOWNS) - return hdl; - break; - - case MT_PC98: - if (flags & MDT_PC98) - return hdl; - break; - - case MT_GM: - case MT_GS: - case MT_MT32: - if (flags & MDT_MIDI) - return hdl; - break; - - case MT_NULL: - return hdl; - - default: - break; - } - - // If the selected driver did not match the flags setting, - // we try to determine a suitable and "optimal" music driver. - const MusicPlugin::List p = MusicMan.getPlugins(); - // If only MDT_MIDI but not MDT_PREFER_MT32 or MDT_PREFER_GM is set we prefer the other devices (which will always be - // detected since they are hard coded and cannot be disabled. - for (int l = (flags & (MDT_PREFER_GM | MDT_PREFER_MT32)) ? 1 : 0; l < 2; ++l) { - if ((flags & MDT_MIDI) && (l == 1)) { - // If a preferred MT32 or GM device has been selected that device gets returned - if (flags & MDT_PREFER_MT32) - hdl = getDeviceHandle(ConfMan.get("mt32_device")); - else if (flags & MDT_PREFER_GM) - hdl = getDeviceHandle(ConfMan.get("gm_device")); - else - hdl = getDeviceHandle("auto"); - - const MusicType type = getMusicType(hdl); - - // If have a "Don't use GM/MT-32" setting we skip this part and jump - // to AdLib, PC Speaker etc. detection right away. - if (type != MT_NULL) { - if (type != MT_AUTO && type != MT_INVALID) { - if (flags & MDT_PREFER_MT32) - // If we have a preferred MT32 device we disable the gm/mt32 mapping (more about this in mididrv.h) - _forceTypeMT32 = true; - - return hdl; - } - - // If we have no specific device selected (neither in the scummvm nor in the game domain) - // and no preferred MT32 or GM device selected we arrive here. - // If MT32 is preferred we try for the first available device with music type 'MT_MT32' (usually the mt32 emulator) - if (flags & MDT_PREFER_MT32) { - for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); ++m) { - MusicDevices i = (**m)->getDevices(); - for (MusicDevices::iterator d = i.begin(); d != i.end(); ++d) { - if (d->getMusicType() == MT_MT32) - return d->getHandle(); - } - } - } - - // Now we default to the first available device with music type 'MT_GM' - for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); ++m) { - MusicDevices i = (**m)->getDevices(); - for (MusicDevices::iterator d = i.begin(); d != i.end(); ++d) { - if (d->getMusicType() == MT_GM || d->getMusicType() == MT_GS) - return d->getHandle(); - } - } - } - } - - MusicType tp = MT_AUTO; - if (flags & MDT_TOWNS) - tp = MT_TOWNS; - else if (flags & MDT_PC98) - tp = MT_PC98; - else if (flags & MDT_ADLIB) - tp = MT_ADLIB; - else if (flags & MDT_PCJR) - tp = MT_PCJR; - else if (flags & MDT_PCSPK) - tp = MT_PCSPK; - else if (flags & MDT_C64) - tp = MT_C64; - else if (flags & MDT_AMIGA) - tp = MT_AMIGA; - else if (flags & MDT_APPLEIIGS) - tp = MT_APPLEIIGS; - else if (l == 0) - // if we haven't tried to find a MIDI device yet we do this now. - continue; - else - tp = MT_AUTO; - - for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); ++m) { - MusicDevices i = (**m)->getDevices(); - for (MusicDevices::iterator d = i.begin(); d != i.end(); ++d) { - if (d->getMusicType() == tp) - return d->getHandle(); - } - } - } - - return 0; -} - -MidiDriver *MidiDriver::createMidi(MidiDriver::DeviceHandle handle) { - MidiDriver *driver = 0; - const MusicPlugin::List p = MusicMan.getPlugins(); - for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); m++) { - if (getDeviceString(handle, MidiDriver::kDriverId).equals((**m)->getId())) - (**m)->createInstance(&driver, handle); - } - - return driver; -} - -MidiDriver::DeviceHandle MidiDriver::getDeviceHandle(const Common::String &identifier) { - const MusicPlugin::List p = MusicMan.getPlugins(); - - if (p.begin() == p.end()) - error("Music plugins must be loaded prior to calling this method"); - - for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); m++) { - MusicDevices i = (**m)->getDevices(); - for (MusicDevices::iterator d = i.begin(); d != i.end(); d++) { - // The music driver id isn't unique, but it will match - // driver's first device. This is useful when selecting - // the driver from the command line. - if (identifier.equals(d->getMusicDriverId()) || identifier.equals(d->getCompleteId()) || identifier.equals(d->getCompleteName())) { - return d->getHandle(); - } - } - } - - return 0; -} - -void MidiDriver::sendMT32Reset() { - static const byte resetSysEx[] = { 0x41, 0x10, 0x16, 0x12, 0x7F, 0x00, 0x00, 0x01, 0x00 }; - sysEx(resetSysEx, sizeof(resetSysEx)); - g_system->delayMillis(100); -} - -void MidiDriver::sendGMReset() { - static const byte resetSysEx[] = { 0x7E, 0x7F, 0x09, 0x01 }; - sysEx(resetSysEx, sizeof(resetSysEx)); - g_system->delayMillis(100); -} - diff --git a/sound/mididrv.h b/sound/mididrv.h deleted file mode 100644 index 9e649cba3d..0000000000 --- a/sound/mididrv.h +++ /dev/null @@ -1,288 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef SOUND_MIDIDRV_H -#define SOUND_MIDIDRV_H - -#include "common/scummsys.h" -#include "common/timer.h" - -class MidiChannel; -class MusicDevice; - -namespace Audio { - class Mixer; -} -namespace Common { class String; } - -/** - * Music Driver Types, used to uniquely identify each music driver. - * - * The pseudo drivers are listed first, then all native drivers, - * then all other MIDI drivers, and finally the non-MIDI drivers. - * - * @todo Rename MidiDriverType to MusicDriverType - */ - -/** - * Music types that music drivers can implement and engines can rely on. - */ -enum MusicType { - MT_INVALID = -1, // Invalid output - MT_AUTO = 0, // Auto - MT_NULL, // Null - MT_PCSPK, // PC Speaker - MT_PCJR, // PCjr - MT_CMS, // CMS - MT_ADLIB, // AdLib - MT_C64, // C64 - MT_AMIGA, // Amiga - MT_APPLEIIGS, // Apple IIGS - MT_TOWNS, // FM-TOWNS - MT_PC98, // PC98 - MT_GM, // General MIDI - MT_MT32, // MT-32 - MT_GS // Roland GS -}; - -/** - * A set of flags to be passed to detectDevice() which can be used to - * specify what kind of music driver is preferred / accepted. - * - * The flags (except for MDT_PREFER_MT32 and MDT_PREFER_GM) indicate whether a given driver - * type is acceptable. E.g. the TOWNS music driver could be returned by - * detectDevice if and only if MDT_TOWNS is specified. - * - * @todo Rename MidiDriverFlags to MusicDriverFlags - */ -enum MidiDriverFlags { - MDT_NONE = 0, - MDT_PCSPK = 1 << 0, // PC Speaker: Maps to MD_PCSPK and MD_PCJR - MDT_CMS = 1 << 1, // Creative Music System / Gameblaster: Maps to MD_CMS - MDT_PCJR = 1 << 2, // Tandy/PC Junior driver - MDT_ADLIB = 1 << 3, // AdLib: Maps to MT_ADLIB - MDT_C64 = 1 << 4, - MDT_AMIGA = 1 << 5, - MDT_APPLEIIGS = 1 << 6, - MDT_TOWNS = 1 << 7, // FM-TOWNS: Maps to MT_TOWNS - MDT_PC98 = 1 << 8, // FM-TOWNS: Maps to MT_PC98 - MDT_MIDI = 1 << 9, // Real MIDI - MDT_PREFER_MT32 = 1 << 10, // MT-32 output is preferred - MDT_PREFER_GM = 1 << 11 // GM output is preferred -}; - -/** - * Abstract description of a MIDI driver. Used by the config file and command - * line parsing code, and also to be able to give the user a list of available - * drivers. - * - * @todo Rename MidiDriverType to MusicDriverType - */ - -/** - * Abstract MIDI Driver Class - * - * @todo Rename MidiDriver to MusicDriver - */ -class MidiDriver { -public: - /** - * The device handle. - * - * The value 0 is reserved for an invalid device for now. - * TODO: Maybe we should use -1 (i.e. 0xFFFFFFFF) as - * invalid device? - */ - typedef uint32 DeviceHandle; - - enum DeviceStringType { - kDriverName, - kDriverId, - kDeviceId - }; - - static uint32 musicType2GUIO(uint32 musicType); - - /** Create music driver matching the given device handle, or NULL if there is no match. */ - static MidiDriver *createMidi(DeviceHandle handle); - - /** Returns device handle based on the present devices and the flags parameter. */ - static DeviceHandle detectDevice(int flags); - - /** Find the music driver matching the given driver name/description. */ - static DeviceHandle getDeviceHandle(const Common::String &identifier); - - /** Get the music type matching the given device handle, or MT_AUTO if there is no match. */ - static MusicType getMusicType(DeviceHandle handle); - - /** Get the device description string matching the given device handle and the given type. */ - static Common::String getDeviceString(DeviceHandle handle, DeviceStringType type); - -private: - // If detectDevice() detects MT32 and we have a preferred MT32 device - // we use this to force getMusicType() to return MT_MT32 so that we don't - // have to rely on the 'True Roland MT-32' config manager setting (since nobody - // would possibly think about activating 'True Roland MT-32' when he has set - // 'Music Driver' to '') - static bool _forceTypeMT32; - -public: - virtual ~MidiDriver() { } - - static const byte _mt32ToGm[128]; - static const byte _gmToMt32[128]; - - /** - * Error codes returned by open. - * Can be converted to a string with getErrorName(). - */ - enum { - MERR_CANNOT_CONNECT = 1, -// MERR_STREAMING_NOT_AVAILABLE = 2, - MERR_DEVICE_NOT_AVAILABLE = 3, - MERR_ALREADY_OPEN = 4 - }; - - enum { -// PROP_TIMEDIV = 1, - PROP_OLD_ADLIB = 2, - PROP_CHANNEL_MASK = 3 - }; - - /** - * Open the midi driver. - * @return 0 if successful, otherwise an error code. - */ - virtual int open() = 0; - - /** Close the midi driver. */ - virtual void close() = 0; - - /** - * Output a packed midi command to the midi stream. - * The 'lowest' byte (i.e. b & 0xFF) is the status - * code, then come (if used) the first and second - * opcode. - */ - virtual void send(uint32 b) = 0; - - /** - * Output a midi command to the midi stream. Convenience wrapper - * around the usual 'packed' send method. - * - * Do NOT use this for sysEx transmission; instead, use the sysEx() - * method below. - */ - void send(byte status, byte firstOp, byte secondOp) { - send(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16)); - } - - /** Get or set a property. */ - virtual uint32 property(int prop, uint32 param) { return 0; } - - /** Retrieve a string representation of an error code. */ - static const char *getErrorName(int error_code); - - // HIGH-LEVEL SEMANTIC METHODS - virtual void setPitchBendRange(byte channel, uint range) { - send(0xB0 | channel, 101, 0); - send(0xB0 | channel, 100, 0); - send(0xB0 | channel, 6, range); - send(0xB0 | channel, 38, 0); - send(0xB0 | channel, 101, 127); - send(0xB0 | channel, 100, 127); - } - - /** - * Send a Roland MT-32 reset sysEx to the midi device. - */ - void sendMT32Reset(); - - /** - * Send a General MIDI reset sysEx to the midi device. - */ - void sendGMReset(); - - /** - * Transmit a sysEx to the midi device. - * - * The given msg MUST NOT contain the usual SysEx frame, i.e. - * do NOT include the leading 0xF0 and the trailing 0xF7. - * - * Furthermore, the maximal supported length of a SysEx - * is 264 bytes. Passing longer buffers can lead to - * undefined behavior (most likely, a crash). - */ - virtual void sysEx(const byte *msg, uint16 length) { } - - virtual void sysEx_customInstrument(byte channel, uint32 type, const byte *instr) { } - - virtual void metaEvent(byte type, byte *data, uint16 length) { } - - // Timing functions - MidiDriver now operates timers - virtual void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) = 0; - - /** The time in microseconds between invocations of the timer callback. */ - virtual uint32 getBaseTempo() = 0; - - // Channel allocation functions - virtual MidiChannel *allocateChannel() = 0; - virtual MidiChannel *getPercussionChannel() = 0; -}; - -class MidiChannel { -public: - virtual ~MidiChannel() {} - - virtual MidiDriver *device() = 0; - virtual byte getNumber() = 0; - virtual void release() = 0; - - virtual void send(uint32 b) = 0; // 4-bit channel portion is ignored - - // Regular messages - virtual void noteOff(byte note) = 0; - virtual void noteOn(byte note, byte velocity) = 0; - virtual void programChange(byte program) = 0; - virtual void pitchBend(int16 bend) = 0; // -0x2000 to +0x1FFF - - // Control Change messages - virtual void controlChange(byte control, byte value) = 0; - virtual void modulationWheel(byte value) { controlChange(1, value); } - virtual void volume(byte value) { controlChange(7, value); } - virtual void panPosition(byte value) { controlChange(10, value); } - virtual void pitchBendFactor(byte value) = 0; - virtual void detune(byte value) { controlChange(17, value); } - virtual void priority(byte value) { } - virtual void sustain(bool value) { controlChange(64, value ? 1 : 0); } - virtual void effectLevel(byte value) { controlChange(91, value); } - virtual void chorusLevel(byte value) { controlChange(93, value); } - virtual void allNotesOff() { controlChange(123, 0); } - - // SysEx messages - virtual void sysEx_customInstrument(uint32 type, const byte *instr) = 0; -}; - -#endif diff --git a/sound/midiparser.cpp b/sound/midiparser.cpp deleted file mode 100644 index bd51a96d46..0000000000 --- a/sound/midiparser.cpp +++ /dev/null @@ -1,467 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/midiparser.h" -#include "sound/mididrv.h" -#include "common/util.h" - -////////////////////////////////////////////////// -// -// MidiParser implementation -// -////////////////////////////////////////////////// - -MidiParser::MidiParser() : -_hanging_notes_count(0), -_driver(0), -_timer_rate(0x4A0000), -_ppqn(96), -_tempo(500000), -_psec_per_tick(5208), // 500000 / 96 -_autoLoop(false), -_smartJump(false), -_centerPitchWheelOnUnload(false), -_sendSustainOffOnNotesOff(false), -_num_tracks(0), -_active_track(255), -_abort_parse(0) { - memset(_active_notes, 0, sizeof(_active_notes)); - _next_event.start = NULL; - _next_event.delta = 0; - _next_event.event = 0; - _next_event.length = 0; -} - -void MidiParser::property(int prop, int value) { - switch (prop) { - case mpAutoLoop: - _autoLoop = (value != 0); - break; - case mpSmartJump: - _smartJump = (value != 0); - break; - case mpCenterPitchWheelOnUnload: - _centerPitchWheelOnUnload = (value != 0); - break; - case mpSendSustainOffOnNotesOff: - _sendSustainOffOnNotesOff = (value != 0); - break; - } -} - -void MidiParser::sendToDriver(uint32 b) { - _driver->send(b); -} - -void MidiParser::setTempo(uint32 tempo) { - _tempo = tempo; - if (_ppqn) - _psec_per_tick = (tempo + (_ppqn >> 2)) / _ppqn; -} - -// This is the conventional (i.e. SMF) variable length quantity -uint32 MidiParser::readVLQ(byte * &data) { - byte str; - uint32 value = 0; - int i; - - for (i = 0; i < 4; ++i) { - str = data[0]; - ++data; - value = (value << 7) | (str & 0x7F); - if (!(str & 0x80)) - break; - } - return value; -} - -void MidiParser::activeNote(byte channel, byte note, bool active) { - if (note >= 128 || channel >= 16) - return; - - if (active) - _active_notes[note] |= (1 << channel); - else - _active_notes[note] &= ~(1 << channel); - - // See if there are hanging notes that we can cancel - NoteTimer *ptr = _hanging_notes; - int i; - for (i = ARRAYSIZE(_hanging_notes); i; --i, ++ptr) { - if (ptr->channel == channel && ptr->note == note && ptr->time_left) { - ptr->time_left = 0; - --_hanging_notes_count; - break; - } - } -} - -void MidiParser::hangingNote(byte channel, byte note, uint32 time_left, bool recycle) { - NoteTimer *best = 0; - NoteTimer *ptr = _hanging_notes; - int i; - - if (_hanging_notes_count >= ARRAYSIZE(_hanging_notes)) { - warning("MidiParser::hangingNote(): Exceeded polyphony"); - return; - } - - for (i = ARRAYSIZE(_hanging_notes); i; --i, ++ptr) { - if (ptr->channel == channel && ptr->note == note) { - if (ptr->time_left && ptr->time_left < time_left && recycle) - return; - best = ptr; - if (ptr->time_left) { - if (recycle) - sendToDriver(0x80 | channel, note, 0); - --_hanging_notes_count; - } - break; - } else if (!best && ptr->time_left == 0) { - best = ptr; - } - } - - // Occassionally we might get a zero or negative note - // length, if the note should be turned on and off in - // the same iteration. For now just set it to 1 and - // we'll turn it off in the next cycle. - if (!time_left || time_left & 0x80000000) - time_left = 1; - - if (best) { - best->channel = channel; - best->note = note; - best->time_left = time_left; - ++_hanging_notes_count; - } else { - // We checked this up top. We should never get here! - warning("MidiParser::hangingNote(): Internal error"); - } -} - -void MidiParser::onTimer() { - uint32 end_time; - uint32 event_time; - - if (!_position._play_pos || !_driver) - return; - - _abort_parse = false; - end_time = _position._play_time + _timer_rate; - - // Scan our hanging notes for any - // that should be turned off. - if (_hanging_notes_count) { - NoteTimer *ptr = &_hanging_notes[0]; - int i; - for (i = ARRAYSIZE(_hanging_notes); i; --i, ++ptr) { - if (ptr->time_left) { - if (ptr->time_left <= _timer_rate) { - sendToDriver(0x80 | ptr->channel, ptr->note, 0); - ptr->time_left = 0; - --_hanging_notes_count; - } else { - ptr->time_left -= _timer_rate; - } - } - } - } - - while (!_abort_parse) { - EventInfo &info = _next_event; - - event_time = _position._last_event_time + info.delta * _psec_per_tick; - if (event_time > end_time) - break; - - // Process the next info. - _position._last_event_tick += info.delta; - if (info.event < 0x80) { - warning("Bad command or running status %02X", info.event); - _position._play_pos = 0; - return; - } - - if (info.event == 0xF0) { - // SysEx event - // Check for trailing 0xF7 -- if present, remove it. - if (info.ext.data[info.length-1] == 0xF7) - _driver->sysEx(info.ext.data, (uint16)info.length-1); - else - _driver->sysEx(info.ext.data, (uint16)info.length); - } else if (info.event == 0xFF) { - // META event - if (info.ext.type == 0x2F) { - // End of Track must be processed by us, - // as well as sending it to the output device. - if (_autoLoop) { - jumpToTick(0); - parseNextEvent(_next_event); - } else { - stopPlaying(); - _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length); - } - return; - } else if (info.ext.type == 0x51) { - if (info.length >= 3) { - setTempo(info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]); - } - } - _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length); - } else { - if (info.command() == 0x8) { - activeNote(info.channel(), info.basic.param1, false); - } else if (info.command() == 0x9) { - if (info.length > 0) - hangingNote(info.channel(), info.basic.param1, info.length * _psec_per_tick - (end_time - event_time)); - else - activeNote(info.channel(), info.basic.param1, true); - } - sendToDriver(info.event, info.basic.param1, info.basic.param2); - } - - - if (!_abort_parse) { - _position._last_event_time = event_time; - parseNextEvent(_next_event); - } - } - - if (!_abort_parse) { - _position._play_time = end_time; - _position._play_tick = (_position._play_time - _position._last_event_time) / _psec_per_tick + _position._last_event_tick; - } -} - -void MidiParser::allNotesOff() { - if (!_driver) - return; - - int i, j; - - // Turn off all active notes - for (i = 0; i < 128; ++i) { - for (j = 0; j < 16; ++j) { - if (_active_notes[i] & (1 << j)) { - sendToDriver(0x80 | j, i, 0); - } - } - } - - // Turn off all hanging notes - for (i = 0; i < ARRAYSIZE(_hanging_notes); i++) { - if (_hanging_notes[i].time_left) { - sendToDriver(0x80 | _hanging_notes[i].channel, _hanging_notes[i].note, 0); - _hanging_notes[i].time_left = 0; - } - } - _hanging_notes_count = 0; - - // To be sure, send an "All Note Off" event (but not all MIDI devices - // support this...). - - for (i = 0; i < 16; ++i) { - sendToDriver(0xB0 | i, 0x7b, 0); // All notes off - if (_sendSustainOffOnNotesOff) - sendToDriver(0xB0 | i, 0x40, 0); // Also send a sustain off event (bug #3116608) - } - - memset(_active_notes, 0, sizeof(_active_notes)); -} - -void MidiParser::resetTracking() { - _position.clear(); -} - -bool MidiParser::setTrack(int track) { - if (track < 0 || track >= _num_tracks) - return false; - // We allow restarting the track via setTrack when - // it isn't playing anymore. This allows us to reuse - // a MidiParser when a track has finished and will - // be restarted via setTrack by the client again. - // This isn't exactly how setTrack behaved before though, - // the old MidiParser code did not allow setTrack to be - // used to restart a track, which was already finished. - // - // TODO: Check if any engine has problem with this - // handling, if so we need to find a better way to handle - // track restarts. (KYRA relies on this working) - else if (track == _active_track && isPlaying()) - return true; - - if (_smartJump) - hangAllActiveNotes(); - else - allNotesOff(); - - resetTracking(); - memset(_active_notes, 0, sizeof(_active_notes)); - _active_track = track; - _position._play_pos = _tracks[track]; - parseNextEvent(_next_event); - return true; -} - -void MidiParser::stopPlaying() { - allNotesOff(); - resetTracking(); -} - -void MidiParser::hangAllActiveNotes() { - // Search for note off events until we have - // accounted for every active note. - uint16 temp_active[128]; - memcpy(temp_active, _active_notes, sizeof (temp_active)); - - uint32 advance_tick = _position._last_event_tick; - while (true) { - int i; - for (i = 0; i < 128; ++i) - if (temp_active[i] != 0) - break; - if (i == 128) - break; - parseNextEvent(_next_event); - advance_tick += _next_event.delta; - if (_next_event.command() == 0x8) { - if (temp_active[_next_event.basic.param1] & (1 << _next_event.channel())) { - hangingNote(_next_event.channel(), _next_event.basic.param1, (advance_tick - _position._last_event_tick) * _psec_per_tick, false); - temp_active[_next_event.basic.param1] &= ~ (1 << _next_event.channel()); - } - } else if (_next_event.event == 0xFF && _next_event.ext.type == 0x2F) { - // warning("MidiParser::hangAllActiveNotes(): Hit End of Track with active notes left"); - for (i = 0; i < 128; ++i) { - for (int j = 0; j < 16; ++j) { - if (temp_active[i] & (1 << j)) { - activeNote(j, i, false); - sendToDriver(0x80 | j, i, 0); - } - } - } - break; - } - } -} - -bool MidiParser::jumpToTick(uint32 tick, bool fireEvents, bool stopNotes, bool dontSendNoteOn) { - if (_active_track >= _num_tracks) - return false; - - Tracker currentPos(_position); - EventInfo currentEvent(_next_event); - - resetTracking(); - _position._play_pos = _tracks[_active_track]; - parseNextEvent(_next_event); - if (tick > 0) { - while (true) { - EventInfo &info = _next_event; - if (_position._last_event_tick + info.delta >= tick) { - _position._play_time += (tick - _position._last_event_tick) * _psec_per_tick; - _position._play_tick = tick; - break; - } - - _position._last_event_tick += info.delta; - _position._last_event_time += info.delta * _psec_per_tick; - _position._play_tick = _position._last_event_tick; - _position._play_time = _position._last_event_time; - - if (info.event == 0xFF) { - if (info.ext.type == 0x2F) { // End of track - _position = currentPos; - _next_event = currentEvent; - return false; - } else { - if (info.ext.type == 0x51 && info.length >= 3) // Tempo - setTempo(info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]); - if (fireEvents) - _driver->metaEvent(info.ext.type, info.ext.data, (uint16) info.length); - } - } else if (fireEvents) { - if (info.event == 0xF0) { - if (info.ext.data[info.length-1] == 0xF7) - _driver->sysEx(info.ext.data, (uint16)info.length-1); - else - _driver->sysEx(info.ext.data, (uint16)info.length); - } else { - // The note on sending code is used by the SCUMM engine. Other engine using this code - // (such as SCI) have issues with this, as all the notes sent can be heard when a song - // is fast-forwarded. Thus, if the engine requests it, don't send note on events. - if (info.command() == 0x9 && dontSendNoteOn) { - // Don't send note on; doing so creates a "warble" with some instruments on the MT-32. - // Refer to patch #3117577 - } else { - sendToDriver(info.event, info.basic.param1, info.basic.param2); - } - } - } - - parseNextEvent(_next_event); - } - } - - if (stopNotes) { - if (!_smartJump || !currentPos._play_pos) { - allNotesOff(); - } else { - EventInfo targetEvent(_next_event); - Tracker targetPosition(_position); - - _position = currentPos; - _next_event = currentEvent; - hangAllActiveNotes(); - - _next_event = targetEvent; - _position = targetPosition; - } - } - - _abort_parse = true; - return true; -} - -void MidiParser::unloadMusic() { - resetTracking(); - allNotesOff(); - _num_tracks = 0; - _active_track = 255; - _abort_parse = true; - - if (_centerPitchWheelOnUnload) { - // Center the pitch wheels in preparation for the next piece of - // music. It's not safe to do this from within allNotesOff(), - // and might not even be safe here, so we only do it if the - // client has explicitly asked for it. - - if (_driver) { - for (int i = 0; i < 16; ++i) { - sendToDriver(0xE0 | i, 0, 0x40); - } - } - } -} diff --git a/sound/midiparser.h b/sound/midiparser.h deleted file mode 100644 index 0b18a19a5b..0000000000 --- a/sound/midiparser.h +++ /dev/null @@ -1,404 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/// \brief Declarations related to the MidiParser class - -#ifndef SOUND_MIDIPARSER_H -#define SOUND_MIDIPARSER_H - -#include "common/scummsys.h" -#include "common/endian.h" - -class MidiParser; -class MidiDriver; - - - -////////////////////////////////////////////////// -// -// Support entities -// -////////////////////////////////////////////////// - -/** - * Maintains time and position state within a MIDI stream. - * A single Tracker struct is used by MidiParser to keep track - * of its current position in the MIDI stream. The Tracker - * struct, however, allows alternative locations to be cached. - * See MidiParser::jumpToTick() for an example of tracking - * multiple locations within a MIDI stream. NOTE: It is - * important to also maintain pre-parsed EventInfo data for - * each Tracker location. - */ -struct Tracker { - byte * _play_pos; ///< A pointer to the next event to be parsed - uint32 _play_time; ///< Current time in microseconds; may be in between event times - uint32 _play_tick; ///< Current MIDI tick; may be in between event ticks - uint32 _last_event_time; ///< The time, in microseconds, of the last event that was parsed - uint32 _last_event_tick; ///< The tick at which the last parsed event occurs - byte _running_status; ///< Cached MIDI command, for MIDI streams that rely on implied event codes - - Tracker() { clear(); } - - /// Copy constructor for each duplication of Tracker information. - Tracker(const Tracker ©) : - _play_pos(copy._play_pos), - _play_time(copy._play_time), - _play_tick(copy._play_tick), - _last_event_time(copy._last_event_time), - _last_event_tick(copy._last_event_tick), - _running_status(copy._running_status) - { } - - /// Clears all data; used by the constructor for initialization. - void clear() { - _play_pos = 0; - _play_time = 0; - _play_tick = 0; - _last_event_time = 0; - _last_event_tick = 0; - _running_status = 0; - } -}; - -/** - * Provides comprehensive information on the next event in the MIDI stream. - * An EventInfo struct is instantiated by format-specific implementations - * of MidiParser::parseNextEvent() each time another event is needed. - */ -struct EventInfo { - byte * start; ///< Position in the MIDI stream where the event starts. - ///< For delta-based MIDI streams (e.g. SMF and XMIDI), this points to the delta. - uint32 delta; ///< The number of ticks after the previous event that this event should occur. - byte event; ///< Upper 4 bits are the command code, lower 4 bits are the MIDI channel. - ///< For META, event == 0xFF. For SysEx, event == 0xF0. - union { - struct { - byte param1; ///< The first parameter in a simple MIDI message. - byte param2; ///< The second parameter in a simple MIDI message. - } basic; - struct { - byte type; ///< For META events, this indicates the META type. - byte * data; ///< For META and SysEx events, this points to the start of the data. - } ext; - }; - uint32 length; ///< For META and SysEx blocks, this indicates the length of the data. - ///< For Note On events, a non-zero value indicates that no Note Off event - ///< will occur, and the MidiParser will have to generate one itself. - ///< For all other events, this value should always be zero. - - byte channel() { return event & 0x0F; } ///< Separates the MIDI channel from the event. - byte command() { return event >> 4; } ///< Separates the command code from the event. -}; - -/** - * Provides expiration tracking for hanging notes. - * Hanging notes are used when a MIDI format does not include explicit Note Off - * events, or when "Smart Jump" is enabled so that active notes are intelligently - * expired when a jump occurs. The NoteTimer struct keeps track of how much - * longer a note should remain active before being turned off. - */ -struct NoteTimer { - byte channel; ///< The MIDI channel on which the note was played - byte note; ///< The note number for the active note - uint32 time_left; ///< The time, in microseconds, remaining before the note should be turned off - NoteTimer() : channel(0), note(0), time_left(0) {} -}; - - - - -////////////////////////////////////////////////// -// -// MidiParser declaration -// -////////////////////////////////////////////////// - -/** - * A framework and common functionality for parsing event-based music streams. - * The MidiParser provides a framework in which to load, - * parse and traverse event-based music data. Note the - * avoidance of the phrase "MIDI data." Despite its name, - * MidiParser derivatives can be used to manage a wide - * variety of event-based music formats. It is, however, - * based on the premise that the format in question can - * be played in the form of specification MIDI events. - * - * In order to use MidiParser to parse your music format, - * follow these steps: - * - * STEP 1: Write a MidiParser derivative. - * The MidiParser base class provides functionality - * considered common to the task of parsing event-based - * music. In order to parse a particular format, create - * a derived class that implements, at minimum, the - * following format-specific methods: - * - loadMusic - * - parseNextEvent - * - * In addition to the above functions, the derived class - * may also override the default MidiParser behavior for - * the following methods: - * - resetTracking - * - allNotesOff - * - unloadMusic - * - property - * - getTick - * - * Please see the documentation for these individual - * functions for more information on their use. - * - * The naming convention for classes derived from - * MidiParser is MidiParser_XXX, where "XXX" is some - * short designator for the format the class will - * support. For instance, the MidiParser derivative - * for parsing the Standard MIDI File format is - * MidiParser_SMF. - * - * STEP 2: Create an object of your derived class. - * Each MidiParser object can parse at most one (1) song - * at a time. However, a MidiParser object can be reused - * to play another song once it is no longer needed to - * play whatever it was playing. In other words, MidiParser - * objects do not have to be destroyed and recreated from - * one song to the next. - * - * STEP 3: Specify a MidiDriver to send events to. - * MidiParser works by sending MIDI and meta events to a - * MidiDriver. In the simplest configuration, you can plug - * a single MidiParser directly into the output MidiDriver - * being used. However, you can only plug in one at a time; - * otherwise channel conflicts will occur. Furthermore, - * meta events that may be needed to interactively control - * music flow cannot be handled because they are being - * sent directly to the output device. - * - * If you need more control over the MidiParser while it's - * playing, you can create your own "pseudo-MidiDriver" and - * place it in between your MidiParser and the output - * MidiDriver. The MidiParser will send events to your - * pseudo-MidiDriver, which in turn must send them to the - * output MidiDriver (or do whatever special handling is - * required). - * - * To specify the MidiDriver to send music output to, - * use the MidiParser::setMidiDriver method. - * - * STEP 4: Specify the onTimer call rate. - * MidiParser bases the timing of its parsing on an external - * clock. Every time MidiParser::onTimer is called, a bit - * more music is parsed. You must specify how many - * microseconds will occur between each call to onTimer, - * in order to ensure an accurate music tempo. - * - * To set the onTimer call rate, in microseconds, - * use the MidiParser::setTimerRate method. The onTimer - * call rate will typically match the timer rate for - * the output MidiDriver used. This rate can be obtained - * by calling MidiDriver::getBaseTempo. - * - * STEP 5: Load the music. - * MidiParser requires that the music data already be loaded - * into memory. The client code is responsible for memory - * management on this block of memory. That means that the - * client code must ensure that the data remain in memory - * while the MidiParser is using it, and properly freed - * after it is no longer needed. Some MidiParser variants may - * require internal buffers as well; memory management for those - * buffers is the responsibility of the MidiParser object. - * - * To load the music into the MidiParser, use the - * MidiParser::loadMusic method, specifying a memory pointer - * to the music data and the size of the data. (NOTE: Some - * MidiParser variants don't require a size, and 0 is fine. - * However, when writing client code to use MidiParser, it is - * best to assume that a valid size will be required. - * - * Convention requires that each implementation of - * MidiParser::loadMusic automatically set up default tempo - * and current track. This effectively means that the - * MidiParser will start playing as soon as timer events - * start coming in. - * - * STEP 6: Activate a timer source for the MidiParser. - * The easiest timer source to use is the timer of the - * output MidiDriver. You can attach the MidiDriver's - * timer output directly to a MidiParser by calling - * MidiDriver::setTimerCallback. In this case, the timer_proc - * will be the static method MidiParser::timerCallback, - * and timer_param will be a pointer to your MidiParser object. - * - * This configuration only allows one MidiParser to be driven - * by the MidiDriver at a time. To drive more MidiDrivers, you - * will need to create a "pseudo-MidiDriver" as described earlier, - * In such a configuration, the pseudo-MidiDriver should be set - * as the timer recipient in MidiDriver::setTimerCallback, and - * could then call MidiParser::onTimer for each MidiParser object. - * - * STEP 7: Music shall begin to play! - * Congratulations! At this point everything should be hooked up - * and the MidiParser should generate music. Note that there is - * no way to "stop" the MidiParser. You can "pause" the MidiParser - * simply by not sending timer events to it, or you can call - * MidiParser::unloadMusic to permanently stop the music. (This - * method resets everything and detaches the MidiParser from the - * memory block containing the music data.) - */ -class MidiParser { -protected: - uint16 _active_notes[128]; ///< Each uint16 is a bit mask for channels that have that note on. - NoteTimer _hanging_notes[32]; ///< Maintains expiration info for up to 32 notes. - ///< Used for "Smart Jump" and MIDI formats that do not include explicit Note Off events. - byte _hanging_notes_count; ///< Count of hanging notes, used to optimize expiration. - - MidiDriver *_driver; ///< The device to which all events will be transmitted. - uint32 _timer_rate; ///< The time in microseconds between onTimer() calls. Obtained from the MidiDriver. - uint32 _ppqn; ///< Pulses Per Quarter Note. (We refer to "pulses" as "ticks".) - uint32 _tempo; ///< Microseconds per quarter note. - uint32 _psec_per_tick; ///< Microseconds per tick (_tempo / _ppqn). - bool _autoLoop; ///< For lightweight clients that don't provide their own flow control. - bool _smartJump; ///< Support smart expiration of hanging notes when jumping - bool _centerPitchWheelOnUnload; ///< Center the pitch wheels when unloading a song - bool _sendSustainOffOnNotesOff; ///< Send a sustain off on a notes off event, stopping hanging notes - byte *_tracks[120]; ///< Multi-track MIDI formats are supported, up to 120 tracks. - byte _num_tracks; ///< Count of total tracks for multi-track MIDI formats. 1 for single-track formats. - byte _active_track; ///< Keeps track of the currently active track, in multi-track formats. - - Tracker _position; ///< The current time/position in the active track. - EventInfo _next_event; ///< The next event to transmit. Events are preparsed - ///< so each event is parsed only once; this permits - ///< simulated events in certain formats. - bool _abort_parse; ///< If a jump or other operation interrupts parsing, flag to abort. - -protected: - static uint32 readVLQ(byte * &data); - virtual void resetTracking(); - virtual void allNotesOff(); - virtual void parseNextEvent(EventInfo &info) = 0; - - void activeNote(byte channel, byte note, bool active); - void hangingNote(byte channel, byte note, uint32 ticks_left, bool recycle = true); - void hangAllActiveNotes(); - - virtual void sendToDriver(uint32 b); - void sendToDriver(byte status, byte firstOp, byte secondOp) { - sendToDriver(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16)); - } - - /** - * Platform independent BE uint32 read-and-advance. - * This helper function reads Big Endian 32-bit numbers - * from a memory pointer, at the same time advancing - * the pointer. - */ - uint32 read4high(byte * &data) { - uint32 val = READ_BE_UINT32(data); - data += 4; - return val; - } - - /** - * Platform independent LE uint16 read-and-advance. - * This helper function reads Little Endian 16-bit numbers - * from a memory pointer, at the same time advancing - * the pointer. - */ - uint16 read2low(byte * &data) { - uint16 val = READ_LE_UINT16(data); - data += 2; - return val; - } - -public: - /** - * Configuration options for MidiParser - * The following options can be set to modify MidiParser's - * behavior. - */ - enum { - /** - * Events containing a pitch bend command should be treated as - * single-byte padding before the real event. This allows the - * MidiParser to work with some malformed SMF files from Simon 1/2. - */ - mpMalformedPitchBends = 1, - - /** - * Sets auto-looping, which can be used by lightweight clients - * that don't provide their own flow control. - */ - mpAutoLoop = 2, - - /** - * Sets smart jumping, which intelligently expires notes that are - * active when a jump is made, rather than just cutting them off. - */ - mpSmartJump = 3, - - /** - * Center the pitch wheels when unloading music in preparation - * for the next piece of music. - */ - mpCenterPitchWheelOnUnload = 4, - - /** - * Sends a sustain off event when a notes off event is triggered. - * Stops hanging notes. - */ - mpSendSustainOffOnNotesOff = 5 - }; - -public: - typedef void (*XMidiCallbackProc)(byte eventData, void *refCon); - - MidiParser(); - virtual ~MidiParser() { allNotesOff(); } - - virtual bool loadMusic(byte *data, uint32 size) = 0; - virtual void unloadMusic(); - virtual void property(int prop, int value); - - void setMidiDriver(MidiDriver *driver) { _driver = driver; } - void setTimerRate(uint32 rate) { _timer_rate = rate; } - void setTempo(uint32 tempo); - void onTimer(); - - bool isPlaying() const { return (_position._play_pos != 0); } - void stopPlaying(); - - bool setTrack(int track); - bool jumpToTick(uint32 tick, bool fireEvents = false, bool stopNotes = true, bool dontSendNoteOn = false); - - uint32 getPPQN() { return _ppqn; } - virtual uint32 getTick() { return _position._play_tick; } - - static void defaultXMidiCallback(byte eventData, void *refCon); - - static MidiParser *createParser_SMF(); - static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0); - static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); } -}; - -#endif diff --git a/sound/midiparser_smf.cpp b/sound/midiparser_smf.cpp deleted file mode 100644 index a9c6f1eb3b..0000000000 --- a/sound/midiparser_smf.cpp +++ /dev/null @@ -1,384 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/midiparser.h" -#include "sound/mididrv.h" -#include "common/util.h" - -/** - * The Standard MIDI File version of MidiParser. - */ -class MidiParser_SMF : public MidiParser { -protected: - byte *_buffer; - bool _malformedPitchBends; - -protected: - void compressToType0(); - void parseNextEvent(EventInfo &info); - -public: - MidiParser_SMF() : _buffer(0), _malformedPitchBends(false) {} - ~MidiParser_SMF(); - - bool loadMusic(byte *data, uint32 size); - void property(int property, int value); -}; - - -static const byte command_lengths[8] = { 3, 3, 3, 3, 2, 2, 3, 0 }; -static const byte special_lengths[16] = { 0, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }; - -MidiParser_SMF::~MidiParser_SMF() { - free(_buffer); -} - -void MidiParser_SMF::property(int prop, int value) { - switch (prop) { - case mpMalformedPitchBends: - _malformedPitchBends = (value > 0); - default: - MidiParser::property(prop, value); - } -} - -void MidiParser_SMF::parseNextEvent(EventInfo &info) { - info.start = _position._play_pos; - info.delta = readVLQ(_position._play_pos); - - // Process the next info. If mpMalformedPitchBends - // was set, we must skip over any pitch bend events - // because they are from Simon games and are not - // real pitch bend events, they're just two-byte - // prefixes before the real info. - do { - if ((_position._play_pos[0] & 0xF0) >= 0x80) - info.event = *(_position._play_pos++); - else - info.event = _position._running_status; - } while (_malformedPitchBends && (info.event & 0xF0) == 0xE0 && _position._play_pos++); - if (info.event < 0x80) - return; - - _position._running_status = info.event; - switch (info.command()) { - case 0x9: // Note On - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = *(_position._play_pos++); - if (info.basic.param2 == 0) - info.event = info.channel() | 0x80; - info.length = 0; - break; - - case 0xC: - case 0xD: - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = 0; - break; - - case 0x8: - case 0xA: - case 0xB: - case 0xE: - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = *(_position._play_pos++); - info.length = 0; - break; - - case 0xF: // System Common, Meta or SysEx event - switch (info.event & 0x0F) { - case 0x2: // Song Position Pointer - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = *(_position._play_pos++); - break; - - case 0x3: // Song Select - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = 0; - break; - - case 0x6: - case 0x8: - case 0xA: - case 0xB: - case 0xC: - case 0xE: - info.basic.param1 = info.basic.param2 = 0; - break; - - case 0x0: // SysEx - info.length = readVLQ(_position._play_pos); - info.ext.data = _position._play_pos; - _position._play_pos += info.length; - break; - - case 0xF: // META event - info.ext.type = *(_position._play_pos++); - info.length = readVLQ(_position._play_pos); - info.ext.data = _position._play_pos; - _position._play_pos += info.length; - break; - - default: - warning("MidiParser_SMF::parseNextEvent: Unsupported event code %x", info.event); - } - } -} - -bool MidiParser_SMF::loadMusic(byte *data, uint32 size) { - uint32 len; - byte midi_type; - uint32 total_size; - bool isGMF; - - unloadMusic(); - byte *pos = data; - isGMF = false; - - if (!memcmp(pos, "RIFF", 4)) { - // Skip the outer RIFF header. - pos += 8; - } - - if (!memcmp(pos, "MThd", 4)) { - // SMF with MTHd information. - pos += 4; - len = read4high(pos); - if (len != 6) { - warning("MThd length 6 expected but found %d", (int)len); - return false; - } - - // Verify that this MIDI either is a Type 2 - // or has only 1 track. We do not support - // multitrack Type 1 files. - _num_tracks = pos[2] << 8 | pos[3]; - midi_type = pos[1]; - if (midi_type > 2 /*|| (midi_type < 2 && _num_tracks > 1)*/) { - warning("No support for a Type %d MIDI with %d tracks", (int)midi_type, (int)_num_tracks); - return false; - } - _ppqn = pos[4] << 8 | pos[5]; - pos += len; - } else if (!memcmp(pos, "GMF\x1", 4)) { - // Older GMD/MUS file with no header info. - // Assume 1 track, 192 PPQN, and no MTrk headers. - isGMF = true; - midi_type = 0; - _num_tracks = 1; - _ppqn = 192; - pos += 7; // 'GMD\x1' + 3 bytes of useless (translate: unknown) information - } else { - warning("Expected MThd or GMD header but found '%c%c%c%c' instead", pos[0], pos[1], pos[2], pos[3]); - return false; - } - - // Now we identify and store the location for each track. - if (_num_tracks > ARRAYSIZE(_tracks)) { - warning("Can only handle %d tracks but was handed %d", (int)ARRAYSIZE(_tracks), (int)_num_tracks); - return false; - } - - total_size = 0; - int tracks_read = 0; - while (tracks_read < _num_tracks) { - if (memcmp(pos, "MTrk", 4) && !isGMF) { - warning("Position: %p ('%c')", pos, *pos); - warning("Hit invalid block '%c%c%c%c' while scanning for track locations", pos[0], pos[1], pos[2], pos[3]); - return false; - } - - // If needed, skip the MTrk and length bytes - _tracks[tracks_read] = pos + (isGMF ? 0 : 8); - if (!isGMF) { - pos += 4; - len = read4high(pos); - total_size += len; - pos += len; - } else { - // An SMF End of Track meta event must be placed - // at the end of the stream. - data[size++] = 0xFF; - data[size++] = 0x2F; - data[size++] = 0x00; - data[size++] = 0x00; - } - ++tracks_read; - } - - // If this is a Type 1 MIDI, we need to now compress - // our tracks down into a single Type 0 track. - free(_buffer); - _buffer = 0; - - if (midi_type == 1) { - // FIXME: Doubled the buffer size to prevent crashes with the - // Inherit the Earth MIDIs. Jamieson630 said something about a - // better fix, but this will have to do in the meantime. - _buffer = (byte *)malloc(size * 2); - compressToType0(); - _num_tracks = 1; - _tracks[0] = _buffer; - } - - // Note that we assume the original data passed in - // will persist beyond this call, i.e. we do NOT - // copy the data to our own buffer. Take warning.... - resetTracking(); - setTempo(500000); - setTrack(0); - return true; -} - -void MidiParser_SMF::compressToType0() { - // We assume that _buffer has been allocated - // to sufficient size for this operation. - - // using 0xFF since it could write track_pos[0 to _num_tracks] here - // this would cause some illegal writes and could lead to segfaults - // (it crashed for some midis for me, they're not used in any game - // scummvm supports though). *Maybe* handle this in another way, - // it's at the moment only to be sure, that nothing goes wrong. - byte *track_pos[0xFF]; - byte running_status[0xFF]; - uint32 track_timer[0xFF]; - uint32 delta; - int i; - - for (i = 0; i < _num_tracks; ++i) { - running_status[i] = 0; - track_pos[i] = _tracks[i]; - track_timer[i] = readVLQ(track_pos[i]); - running_status[i] = 0; - } - - int best_i; - uint32 length; - byte *output = _buffer; - byte *pos, *pos2; - byte event; - uint32 copy_bytes; - bool write; - byte active_tracks = (byte)_num_tracks; - - while (active_tracks) { - write = true; - best_i = 255; - for (i = 0; i < _num_tracks; ++i) { - if (track_pos[i] && (best_i == 255 || track_timer[i] < track_timer[best_i])) - best_i = i; - } - if (best_i == 255) { - warning("Premature end of tracks"); - break; - } - - // Initial VLQ delta computation - delta = 0; - length = track_timer[best_i]; - for (i = 0; length; ++i) { - delta = (delta << 8) | (length & 0x7F) | (i ? 0x80 : 0); - length >>= 7; - } - - // Process MIDI event. - bool implicitEvent = false; - copy_bytes = 0; - pos = track_pos[best_i]; - do { - event = *(pos++); - if (event < 0x80) { - event = running_status[best_i]; - implicitEvent = true; - } - } while (_malformedPitchBends && (event & 0xF0) == 0xE0 && pos++); - running_status[best_i] = event; - - if (command_lengths[(event >> 4) - 8] > 0) { - copy_bytes = command_lengths[(event >> 4) - 8]; - } else if (special_lengths[(event & 0x0F)] > 0) { - copy_bytes = special_lengths[(event & 0x0F)]; - } else if (event == 0xF0) { - // SysEx - pos2 = pos; - length = readVLQ(pos); - copy_bytes = 1 + (pos - pos2) + length; - } else if (event == 0xFF) { - // META - event = *(pos++); - if (event == 0x2F && active_tracks > 1) { - track_pos[best_i] = 0; - write = false; - } else { - pos2 = pos; - length = readVLQ(pos); - copy_bytes = 2 + (pos - pos2) + length; - } - if (event == 0x2F) - --active_tracks; - } else { - warning("Bad MIDI command %02X", (int)event); - track_pos[best_i] = 0; - } - - // Update all tracks' deltas - if (write) { - for (i = 0; i < _num_tracks; ++i) { - if (track_pos[i] && i != best_i) - track_timer[i] -= track_timer[best_i]; - } - } - - if (track_pos[best_i]) { - if (write) { - track_timer[best_i] = 0; - - // Write VLQ delta - while (delta & 0x80) { - *output++ = (byte)(delta & 0xFF); - delta >>= 8; - } - *output++ = (byte)(delta & 0xFF); - - // Write MIDI data - if (!implicitEvent) - ++track_pos[best_i]; - --copy_bytes; - *output++ = running_status[best_i]; - memcpy(output, track_pos[best_i], copy_bytes); - output += copy_bytes; - } - - // Fetch new VLQ delta for winning track - track_pos[best_i] += copy_bytes; - if (active_tracks) - track_timer[best_i] += readVLQ(track_pos[best_i]); - } - } - - *output++ = 0x00; -} - -MidiParser *MidiParser::createParser_SMF() { return new MidiParser_SMF; } diff --git a/sound/midiparser_xmidi.cpp b/sound/midiparser_xmidi.cpp deleted file mode 100644 index 343ca34659..0000000000 --- a/sound/midiparser_xmidi.cpp +++ /dev/null @@ -1,375 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/midiparser.h" -#include "sound/mididrv.h" -#include "common/util.h" - -/** - * The XMIDI version of MidiParser. - * - * Much of this code is adapted from the XMIDI implementation from the exult - * project. - */ -class MidiParser_XMIDI : public MidiParser { -protected: - NoteTimer _notes_cache[32]; - uint32 _inserted_delta; // Track simulated deltas for note-off events - - struct Loop { - byte *pos; - byte repeat; - }; - - Loop _loop[4]; - int _loopCount; - - XMidiCallbackProc _callbackProc; - void *_callbackData; - -protected: - uint32 readVLQ2(byte * &data); - void resetTracking(); - void parseNextEvent(EventInfo &info); - -public: - MidiParser_XMIDI(XMidiCallbackProc proc, void *data) : _inserted_delta(0), _callbackProc(proc), _callbackData(data) {} - ~MidiParser_XMIDI() { } - - bool loadMusic(byte *data, uint32 size); -}; - - -// This is a special XMIDI variable length quantity -uint32 MidiParser_XMIDI::readVLQ2(byte * &pos) { - uint32 value = 0; - while (!(pos[0] & 0x80)) { - value += *pos++; - } - return value; -} - -void MidiParser_XMIDI::parseNextEvent(EventInfo &info) { - info.start = _position._play_pos; - info.delta = readVLQ2(_position._play_pos) - _inserted_delta; - - // Process the next event. - _inserted_delta = 0; - info.event = *(_position._play_pos++); - switch (info.event >> 4) { - case 0x9: // Note On - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = *(_position._play_pos++); - info.length = readVLQ(_position._play_pos); - if (info.basic.param2 == 0) { - info.event = info.channel() | 0x80; - info.length = 0; - } - break; - - case 0xC: - case 0xD: - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = 0; - break; - - case 0x8: - case 0xA: - case 0xE: - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = *(_position._play_pos++); - break; - - case 0xB: - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = *(_position._play_pos++); - - // This isn't a full XMIDI implementation, but it should - // hopefully be "good enough" for most things. - - switch (info.basic.param1) { - // Simplified XMIDI looping. - case 0x74: { // XMIDI_CONTROLLER_FOR_LOOP - byte *pos = _position._play_pos; - if (_loopCount < ARRAYSIZE(_loop) - 1) - _loopCount++; - else - warning("XMIDI: Exceeding maximum loop count %d", ARRAYSIZE(_loop)); - - _loop[_loopCount].pos = pos; - _loop[_loopCount].repeat = info.basic.param2; - break; - } - - case 0x75: // XMIDI_CONTORLLER_NEXT_BREAK - if (_loopCount >= 0) { - if (info.basic.param2 < 64) { - // End the current loop. - _loopCount--; - } else { - // Repeat 0 means "loop forever". - if (_loop[_loopCount].repeat) { - if (--_loop[_loopCount].repeat == 0) - _loopCount--; - else - _position._play_pos = _loop[_loopCount].pos; - } else { - _position._play_pos = _loop[_loopCount].pos; - } - } - } - break; - - case 0x77: // XMIDI_CONTROLLER_CALLBACK_TRIG - if (_callbackProc) - _callbackProc(info.basic.param2, _callbackData); - break; - - case 0x6e: // XMIDI_CONTROLLER_CHAN_LOCK - case 0x6f: // XMIDI_CONTROLLER_CHAN_LOCK_PROT - case 0x70: // XMIDI_CONTROLLER_VOICE_PROT - case 0x71: // XMIDI_CONTROLLER_TIMBRE_PROT - case 0x72: // XMIDI_CONTROLLER_BANK_CHANGE - case 0x73: // XMIDI_CONTROLLER_IND_CTRL_PREFIX - case 0x76: // XMIDI_CONTROLLER_CLEAR_BB_COUNT - case 0x78: // XMIDI_CONTROLLER_SEQ_BRANCH_INDEX - default: - if (info.basic.param1 >= 0x6e && info.basic.param1 <= 0x78) { - warning("Unsupported XMIDI controller %d (0x%2x)", - info.basic.param1, info.basic.param1); - } - } - - // Should we really keep passing the XMIDI controller events to - // the MIDI driver, or should we turn them into some kind of - // NOP events? (Dummy meta events, perhaps?) Ah well, it has - // worked so far, so it shouldn't cause any damage... - - break; - - case 0xF: // Meta or SysEx event - switch (info.event & 0x0F) { - case 0x2: // Song Position Pointer - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = *(_position._play_pos++); - break; - - case 0x3: // Song Select - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = 0; - break; - - case 0x6: - case 0x8: - case 0xA: - case 0xB: - case 0xC: - case 0xE: - info.basic.param1 = info.basic.param2 = 0; - break; - - case 0x0: // SysEx - info.length = readVLQ(_position._play_pos); - info.ext.data = _position._play_pos; - _position._play_pos += info.length; - break; - - case 0xF: // META event - info.ext.type = *(_position._play_pos++); - info.length = readVLQ(_position._play_pos); - info.ext.data = _position._play_pos; - _position._play_pos += info.length; - if (info.ext.type == 0x51 && info.length == 3) { - // Tempo event. We want to make these constant 500,000. - info.ext.data[0] = 0x07; - info.ext.data[1] = 0xA1; - info.ext.data[2] = 0x20; - } - break; - - default: - warning("MidiParser_XMIDI::parseNextEvent: Unsupported event code %x", info.event); - } - } -} - -bool MidiParser_XMIDI::loadMusic(byte *data, uint32 size) { - uint32 i = 0; - byte *start; - uint32 len; - uint32 chunk_len; - char buf[32]; - - _loopCount = -1; - - unloadMusic(); - byte *pos = data; - - if (!memcmp(pos, "FORM", 4)) { - pos += 4; - - // Read length of - len = read4high(pos); - start = pos; - - // XDIRless XMIDI, we can handle them here. - if (!memcmp(pos, "XMID", 4)) { - warning("XMIDI doesn't have XDIR"); - pos += 4; - _num_tracks = 1; - } else if (memcmp(pos, "XDIR", 4)) { - // Not an XMIDI that we recognise - warning("Expected 'XDIR' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]); - return false; - } else { - // Seems Valid - pos += 4; - _num_tracks = 0; - - for (i = 4; i < len; i++) { - // Read 4 bytes of type - memcpy(buf, pos, 4); - pos += 4; - - // Read length of chunk - chunk_len = read4high(pos); - - // Add eight bytes - i += 8; - - if (memcmp(buf, "INFO", 4)) { - // Must align - pos += (chunk_len + 1) & ~1; - i += (chunk_len + 1) & ~1; - continue; - } - - // Must be at least 2 bytes long - if (chunk_len < 2) { - warning("Invalid chunk length %d for 'INFO' block", (int)chunk_len); - return false; - } - - _num_tracks = (byte)read2low(pos); - - if (chunk_len > 2) { - warning("Chunk length %d is greater than 2", (int)chunk_len); - pos += chunk_len - 2; - } - break; - } - - // Didn't get to fill the header - if (_num_tracks == 0) { - warning("Didn't find a valid track count"); - return false; - } - - // Ok now to start part 2 - // Goto the right place - pos = start + ((len + 1) & ~1); - - if (memcmp(pos, "CAT ", 4)) { - // Not an XMID - warning("Expected 'CAT ' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]); - return false; - } - pos += 4; - - // Now read length of this track - len = read4high(pos); - - if (memcmp(pos, "XMID", 4)) { - // Not an XMID - warning("Expected 'XMID' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]); - return false; - } - pos += 4; - - } - - // Ok it's an XMIDI. - // We're going to identify and store the location for each track. - if (_num_tracks > ARRAYSIZE(_tracks)) { - warning("Can only handle %d tracks but was handed %d", (int)ARRAYSIZE(_tracks), (int)_num_tracks); - return false; - } - - int tracks_read = 0; - while (tracks_read < _num_tracks) { - if (!memcmp(pos, "FORM", 4)) { - // Skip this plus the 4 bytes after it. - pos += 8; - } else if (!memcmp(pos, "XMID", 4)) { - // Skip this. - pos += 4; - } else if (!memcmp(pos, "TIMB", 4)) { - // Custom timbres? - // We don't support them. - // Read the length, skip it, and hope there was nothing there. - pos += 4; - len = read4high(pos); - pos += (len + 1) & ~1; - } else if (!memcmp(pos, "EVNT", 4)) { - // Ahh! What we're looking for at last. - _tracks[tracks_read] = pos + 8; // Skip the EVNT and length bytes - pos += 4; - len = read4high(pos); - pos += (len + 1) & ~1; - ++tracks_read; - } else { - warning("Hit invalid block '%c%c%c%c' while scanning for track locations", pos[0], pos[1], pos[2], pos[3]); - return false; - } - } - - // If we got this far, we successfully established - // the locations for each of our tracks. - // Note that we assume the original data passed in - // will persist beyond this call, i.e. we do NOT - // copy the data to our own buffer. Take warning.... - _ppqn = 60; - resetTracking(); - setTempo(500000); - _inserted_delta = 0; - setTrack(0); - return true; - } - - return false; -} - -void MidiParser_XMIDI::resetTracking() { - MidiParser::resetTracking(); - _inserted_delta = 0; -} - -void MidiParser::defaultXMidiCallback(byte eventData, void *data) { - warning("MidiParser: defaultXMidiCallback(%d)", eventData); -} - -MidiParser *MidiParser::createParser_XMIDI(XMidiCallbackProc proc, void *data) { - return new MidiParser_XMIDI(proc, data); -} diff --git a/sound/mixer.cpp b/sound/mixer.cpp deleted file mode 100644 index c40aa95d73..0000000000 --- a/sound/mixer.cpp +++ /dev/null @@ -1,556 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "common/util.h" -#include "common/system.h" - -#include "sound/mixer_intern.h" -#include "sound/rate.h" -#include "sound/audiostream.h" -#include "sound/timestamp.h" - - -namespace Audio { - -#pragma mark - -#pragma mark --- Channel classes --- -#pragma mark - - - -/** - * Channel used by the default Mixer implementation. - */ -class Channel { -public: - Channel(Mixer *mixer, Mixer::SoundType type, AudioStream *stream, DisposeAfterUse::Flag autofreeStream, bool reverseStereo, int id, bool permanent); - ~Channel(); - - /** - * Mixes the channel's samples into the given buffer. - * - * @param data buffer where to mix the data - * @param len number of sample *pairs*. So a value of - * 10 means that the buffer contains twice 10 sample, each - * 16 bits, for a total of 40 bytes. - */ - void mix(int16 *data, uint len); - - /** - * Queries whether the channel is still playing or not. - */ - bool isFinished() const { return _stream->endOfStream(); } - - /** - * Queries whether the channel is a permanent channel. - * A permanent channel is not affected by a Mixer::stopAll - * call. - */ - bool isPermanent() const { return _permanent; } - - /** - * Returns the id of the channel. - */ - int getId() const { return _id; } - - /** - * Pauses or unpaused the channel in a recursive fashion. - * - * @param paused true, when the channel should be paused. - * false when it should be unpaused. - */ - void pause(bool paused); - - /** - * Queries whether the channel is currently paused. - */ - bool isPaused() const { return (_pauseLevel != 0); } - - /** - * Sets the channel's own volume. - * - * @param volume new volume - */ - void setVolume(const byte volume); - - /** - * Sets the channel's balance setting. - * - * @param balance new balance - */ - void setBalance(const int8 balance); - - /** - * Notifies the channel that the global sound type - * volume settings changed. - */ - void notifyGlobalVolChange() { updateChannelVolumes(); } - - /** - * Queries how long the channel has been playing. - */ - Timestamp getElapsedTime(); - - /** - * Queries the channel's sound type. - */ - Mixer::SoundType getType() const { return _type; } - - /** - * Sets the channel's sound handle. - * - * @param handle new handle - */ - void setHandle(const SoundHandle handle) { _handle = handle; } - - /** - * Queries the channel's sound handle. - */ - SoundHandle getHandle() const { return _handle; } - -private: - const Mixer::SoundType _type; - SoundHandle _handle; - bool _permanent; - int _pauseLevel; - int _id; - - byte _volume; - int8 _balance; - - void updateChannelVolumes(); - st_volume_t _volL, _volR; - - Mixer *_mixer; - - uint32 _samplesConsumed; - uint32 _samplesDecoded; - uint32 _mixerTimeStamp; - uint32 _pauseStartTime; - uint32 _pauseTime; - - DisposeAfterUse::Flag _autofreeStream; - RateConverter *_converter; - AudioStream *_stream; -}; - -#pragma mark - -#pragma mark --- Mixer --- -#pragma mark - - - -MixerImpl::MixerImpl(OSystem *system, uint sampleRate) - : _syst(system), _sampleRate(sampleRate), _mixerReady(false), _handleSeed(0) { - - assert(sampleRate > 0); - - int i; - - for (i = 0; i < ARRAYSIZE(_volumeForSoundType); i++) - _volumeForSoundType[i] = kMaxMixerVolume; - - for (i = 0; i != NUM_CHANNELS; i++) - _channels[i] = 0; -} - -MixerImpl::~MixerImpl() { - for (int i = 0; i != NUM_CHANNELS; i++) - delete _channels[i]; -} - -void MixerImpl::setReady(bool ready) { - _mixerReady = ready; -} - -uint MixerImpl::getOutputRate() const { - return _sampleRate; -} - -void MixerImpl::insertChannel(SoundHandle *handle, Channel *chan) { - int index = -1; - for (int i = 0; i != NUM_CHANNELS; i++) { - if (_channels[i] == 0) { - index = i; - break; - } - } - if (index == -1) { - warning("MixerImpl::out of mixer slots"); - delete chan; - return; - } - - _channels[index] = chan; - - SoundHandle chanHandle; - chanHandle._val = index + (_handleSeed * NUM_CHANNELS); - - chan->setHandle(chanHandle); - _handleSeed++; - if (handle) - *handle = chanHandle; -} - -void MixerImpl::playStream( - SoundType type, - SoundHandle *handle, - AudioStream *stream, - int id, byte volume, int8 balance, - DisposeAfterUse::Flag autofreeStream, - bool permanent, - bool reverseStereo) { - Common::StackLock lock(_mutex); - - if (stream == 0) { - warning("stream is 0"); - return; - } - - - assert(_mixerReady); - - // Prevent duplicate sounds - if (id != -1) { - for (int i = 0; i != NUM_CHANNELS; i++) - if (_channels[i] != 0 && _channels[i]->getId() == id) { - // Delete the stream if were asked to auto-dispose it. - // Note: This could cause trouble if the client code does not - // yet expect the stream to be gone. The primary example to - // keep in mind here is QueuingAudioStream. - // Thus, as a quick rule of thumb, you should never, ever, - // try to play QueuingAudioStreams with a sound id. - if (autofreeStream == DisposeAfterUse::YES) - delete stream; - return; - } - } - -#ifdef AUDIO_REVERSE_STEREO - reverseStereo = !reverseStereo; -#endif - - // Create the channel - Channel *chan = new Channel(this, type, stream, autofreeStream, reverseStereo, id, permanent); - chan->setVolume(volume); - chan->setBalance(balance); - insertChannel(handle, chan); -} - -void MixerImpl::mixCallback(byte *samples, uint len) { - assert(samples); - - Common::StackLock lock(_mutex); - - int16 *buf = (int16 *)samples; - len >>= 2; - - // Since the mixer callback has been called, the mixer must be ready... - _mixerReady = true; - - // zero the buf - memset(buf, 0, 2 * len * sizeof(int16)); - - // mix all channels - for (int i = 0; i != NUM_CHANNELS; i++) - if (_channels[i]) { - if (_channels[i]->isFinished()) { - delete _channels[i]; - _channels[i] = 0; - } else if (!_channels[i]->isPaused()) - _channels[i]->mix(buf, len); - } -} - -void MixerImpl::stopAll() { - Common::StackLock lock(_mutex); - for (int i = 0; i != NUM_CHANNELS; i++) { - if (_channels[i] != 0 && !_channels[i]->isPermanent()) { - delete _channels[i]; - _channels[i] = 0; - } - } -} - -void MixerImpl::stopID(int id) { - Common::StackLock lock(_mutex); - for (int i = 0; i != NUM_CHANNELS; i++) { - if (_channels[i] != 0 && _channels[i]->getId() == id) { - delete _channels[i]; - _channels[i] = 0; - } - } -} - -void MixerImpl::stopHandle(SoundHandle handle) { - Common::StackLock lock(_mutex); - - // Simply ignore stop requests for handles of sounds that already terminated - const int index = handle._val % NUM_CHANNELS; - if (!_channels[index] || _channels[index]->getHandle()._val != handle._val) - return; - - delete _channels[index]; - _channels[index] = 0; -} - -void MixerImpl::setChannelVolume(SoundHandle handle, byte volume) { - Common::StackLock lock(_mutex); - - const int index = handle._val % NUM_CHANNELS; - if (!_channels[index] || _channels[index]->getHandle()._val != handle._val) - return; - - _channels[index]->setVolume(volume); -} - -void MixerImpl::setChannelBalance(SoundHandle handle, int8 balance) { - Common::StackLock lock(_mutex); - - const int index = handle._val % NUM_CHANNELS; - if (!_channels[index] || _channels[index]->getHandle()._val != handle._val) - return; - - _channels[index]->setBalance(balance); -} - -uint32 MixerImpl::getSoundElapsedTime(SoundHandle handle) { - return getElapsedTime(handle).msecs(); -} - -Timestamp MixerImpl::getElapsedTime(SoundHandle handle) { - Common::StackLock lock(_mutex); - - const int index = handle._val % NUM_CHANNELS; - if (!_channels[index] || _channels[index]->getHandle()._val != handle._val) - return Timestamp(0, _sampleRate); - - return _channels[index]->getElapsedTime(); -} - -void MixerImpl::pauseAll(bool paused) { - Common::StackLock lock(_mutex); - for (int i = 0; i != NUM_CHANNELS; i++) { - if (_channels[i] != 0) { - _channels[i]->pause(paused); - } - } -} - -void MixerImpl::pauseID(int id, bool paused) { - Common::StackLock lock(_mutex); - for (int i = 0; i != NUM_CHANNELS; i++) { - if (_channels[i] != 0 && _channels[i]->getId() == id) { - _channels[i]->pause(paused); - return; - } - } -} - -void MixerImpl::pauseHandle(SoundHandle handle, bool paused) { - Common::StackLock lock(_mutex); - - // Simply ignore (un)pause requests for sounds that already terminated - const int index = handle._val % NUM_CHANNELS; - if (!_channels[index] || _channels[index]->getHandle()._val != handle._val) - return; - - _channels[index]->pause(paused); -} - -bool MixerImpl::isSoundIDActive(int id) { - Common::StackLock lock(_mutex); - for (int i = 0; i != NUM_CHANNELS; i++) - if (_channels[i] && _channels[i]->getId() == id) - return true; - return false; -} - -int MixerImpl::getSoundID(SoundHandle handle) { - Common::StackLock lock(_mutex); - const int index = handle._val % NUM_CHANNELS; - if (_channels[index] && _channels[index]->getHandle()._val == handle._val) - return _channels[index]->getId(); - return 0; -} - -bool MixerImpl::isSoundHandleActive(SoundHandle handle) { - Common::StackLock lock(_mutex); - const int index = handle._val % NUM_CHANNELS; - return _channels[index] && _channels[index]->getHandle()._val == handle._val; -} - -bool MixerImpl::hasActiveChannelOfType(SoundType type) { - Common::StackLock lock(_mutex); - for (int i = 0; i != NUM_CHANNELS; i++) - if (_channels[i] && _channels[i]->getType() == type) - return true; - return false; -} - -void MixerImpl::setVolumeForSoundType(SoundType type, int volume) { - assert(0 <= type && type < ARRAYSIZE(_volumeForSoundType)); - - // Check range - if (volume > kMaxMixerVolume) - volume = kMaxMixerVolume; - else if (volume < 0) - volume = 0; - - // TODO: Maybe we should do logarithmic (not linear) volume - // scaling? See also Player_V2::setMasterVolume - - Common::StackLock lock(_mutex); - _volumeForSoundType[type] = volume; - - for (int i = 0; i != NUM_CHANNELS; ++i) { - if (_channels[i] && _channels[i]->getType() == type) - _channels[i]->notifyGlobalVolChange(); - } -} - -int MixerImpl::getVolumeForSoundType(SoundType type) const { - assert(0 <= type && type < ARRAYSIZE(_volumeForSoundType)); - - return _volumeForSoundType[type]; -} - - -#pragma mark - -#pragma mark --- Channel implementations --- -#pragma mark - - -Channel::Channel(Mixer *mixer, Mixer::SoundType type, AudioStream *stream, - DisposeAfterUse::Flag autofreeStream, bool reverseStereo, int id, bool permanent) - : _type(type), _mixer(mixer), _id(id), _permanent(permanent), _volume(Mixer::kMaxChannelVolume), - _balance(0), _pauseLevel(0), _samplesConsumed(0), _samplesDecoded(0), _mixerTimeStamp(0), - _pauseStartTime(0), _pauseTime(0), _autofreeStream(autofreeStream), _converter(0), - _stream(stream) { - assert(mixer); - assert(stream); - - // Get a rate converter instance - _converter = makeRateConverter(_stream->getRate(), mixer->getOutputRate(), _stream->isStereo(), reverseStereo); -} - -Channel::~Channel() { - delete _converter; - if (_autofreeStream == DisposeAfterUse::YES) - delete _stream; -} - -void Channel::setVolume(const byte volume) { - _volume = volume; - updateChannelVolumes(); -} - -void Channel::setBalance(const int8 balance) { - _balance = balance; - updateChannelVolumes(); -} - -void Channel::updateChannelVolumes() { - // From the channel balance/volume and the global volume, we compute - // the effective volume for the left and right channel. Note the - // slightly odd divisor: the 255 reflects the fact that the maximal - // value for _volume is 255, while the 127 is there because the - // balance value ranges from -127 to 127. The mixer (music/sound) - // volume is in the range 0 - kMaxMixerVolume. - // Hence, the vol_l/vol_r values will be in that range, too - - int vol = _mixer->getVolumeForSoundType(_type) * _volume; - - if (_balance == 0) { - _volL = vol / Mixer::kMaxChannelVolume; - _volR = vol / Mixer::kMaxChannelVolume; - } else if (_balance < 0) { - _volL = vol / Mixer::kMaxChannelVolume; - _volR = ((127 + _balance) * vol) / (Mixer::kMaxChannelVolume * 127); - } else { - _volL = ((127 - _balance) * vol) / (Mixer::kMaxChannelVolume * 127); - _volR = vol / Mixer::kMaxChannelVolume; - } -} - -void Channel::pause(bool paused) { - //assert((paused && _pauseLevel >= 0) || (!paused && _pauseLevel)); - - if (paused) { - _pauseLevel++; - - if (_pauseLevel == 1) - _pauseStartTime = g_system->getMillis(); - } else if (_pauseLevel > 0) { - _pauseLevel--; - - if (!_pauseLevel) { - _pauseTime = (g_system->getMillis() - _pauseStartTime); - _pauseStartTime = 0; - } - } -} - -Timestamp Channel::getElapsedTime() { - const uint32 rate = _mixer->getOutputRate(); - uint32 delta = 0; - - Audio::Timestamp ts(0, rate); - - if (_mixerTimeStamp == 0) - return ts; - - if (isPaused()) - delta = _pauseStartTime - _mixerTimeStamp; - else - delta = g_system->getMillis() - _mixerTimeStamp - _pauseTime; - - // Convert the number of samples into a time duration. - - ts = ts.addFrames(_samplesConsumed); - ts = ts.addMsecs(delta); - - // In theory it would seem like a good idea to limit the approximation - // so that it never exceeds the theoretical upper bound set by - // _samplesDecoded. Meanwhile, back in the real world, doing so makes - // the Broken Sword cutscenes noticeably jerkier. I guess the mixer - // isn't invoked at the regular intervals that I first imagined. - - return ts; -} - -void Channel::mix(int16 *data, uint len) { - assert(_stream); - - if (_stream->endOfData()) { - // TODO: call drain method - } else { - assert(_converter); - - _samplesConsumed = _samplesDecoded; - _mixerTimeStamp = g_system->getMillis(); - _pauseTime = 0; - _samplesDecoded += _converter->flow(*_stream, data, len, _volL, _volR); - } -} - -} // End of namespace Audio diff --git a/sound/mixer.h b/sound/mixer.h deleted file mode 100644 index b2accc550d..0000000000 --- a/sound/mixer.h +++ /dev/null @@ -1,265 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef SOUND_MIXER_H -#define SOUND_MIXER_H - -#include "common/types.h" -#include "common/mutex.h" -#include "common/noncopyable.h" - -#include "sound/timestamp.h" - -class OSystem; - - -namespace Audio { - -class AudioStream; -class Channel; -class Mixer; -class MixerImpl; - -/** - * A SoundHandle instances corresponds to a specific sound - * being played via the mixer. It can be used to control that - * sound (pause it, stop it, etc.). - * @see The Mixer class - */ -class SoundHandle { - friend class Channel; - friend class MixerImpl; - uint32 _val; -public: - inline SoundHandle() : _val(0xFFFFFFFF) {} -}; - -/** - * The main audio mixer handles mixing of an arbitrary number of - * audio streams (in the form of AudioStream instances). - */ -class Mixer : Common::NonCopyable { -public: - enum SoundType { - kPlainSoundType = 0, - - kMusicSoundType = 1, - kSFXSoundType = 2, - kSpeechSoundType = 3 - }; - - enum { - kMaxChannelVolume = 255, - kMaxMixerVolume = 256 - }; - -public: - Mixer() {} - virtual ~Mixer() {} - - - - /** - * Is the mixer ready and setup? This may not be the case on systems which - * don't support digital sound output. In that case, the mixer proc may - * never be called. That in turn can cause breakage in games which try to - * sync with an audio stream. In particular, the AdLib MIDI emulation... - * - * @return whether the mixer is ready and setup - * - * @todo get rid of this? - */ - virtual bool isReady() const = 0; - - - /** - * Start playing the given audio stream. - * - * Note that the sound id assigned below is unique. At most one stream - * with a given id can play at any given time. Trying to play a sound - * with an id that is already in use causes the new sound to be not played. - * - * @param type the type (voice/sfx/music) of the stream - * @param handle a SoundHandle which can be used to reference and control - * the stream via suitable mixer methods - * @param stream the actual AudioStream to be played - * @param id a unique id assigned to this stream - * @param volume the volume with which to play the sound, ranging from 0 to 255 - * @param balance the balance with which to play the sound, ranging from -128 to 127 - * @param autofreeStream a flag indicating whether the stream should be - * freed after playback finished - * @param permanent a flag indicating whether a plain stopAll call should - * not stop this particular stream - * @param reverseStereo a flag indicating whether left and right channels shall be swapped - */ - virtual void playStream( - SoundType type, - SoundHandle *handle, - AudioStream *stream, - int id = -1, - byte volume = kMaxChannelVolume, - int8 balance = 0, - DisposeAfterUse::Flag autofreeStream = DisposeAfterUse::YES, - bool permanent = false, - bool reverseStereo = false) = 0; - - /** - * Stop all currently playing sounds. - */ - virtual void stopAll() = 0; - - /** - * Stop playing the sound with given ID. - * - * @param id the ID of the sound to affect - */ - virtual void stopID(int id) = 0; - - /** - * Stop playing the sound corresponding to the given handle. - * - * @param handle the sound to affect - */ - virtual void stopHandle(SoundHandle handle) = 0; - - - - /** - * Pause/unpause all sounds, including all regular and permanent - * channels - * - * @param paused true to pause everything, false to unpause - */ - virtual void pauseAll(bool paused) = 0; - - /** - * Pause/unpause the sound with the given ID. - * - * @param id the ID of the sound to affect - * @param paused true to pause the sound, false to unpause it - */ - virtual void pauseID(int id, bool paused) = 0; - - /** - * Pause/unpause the sound corresponding to the given handle. - * - * @param handle the sound to affect - * @param paused true to pause the sound, false to unpause it - */ - virtual void pauseHandle(SoundHandle handle, bool paused) = 0; - - - - /** - * Check if a sound with the given ID is active. - * - * @param id the ID of the sound to query - * @return true if the sound is active - */ - virtual bool isSoundIDActive(int id) = 0; - - /** - * Get the sound ID of handle sound - * - * @param handle sound to query - * @return sound ID if active - */ - virtual int getSoundID(SoundHandle handle) = 0; - - /** - * Check if a sound with the given handle is active. - * - * @param handle sound to query - * @return true if the sound is active - */ - virtual bool isSoundHandleActive(SoundHandle handle) = 0; - - - - /** - * Set the channel volume for the given handle. - * - * @param handle the sound to affect - * @param volume the new channel volume (0 - kMaxChannelVolume) - */ - virtual void setChannelVolume(SoundHandle handle, byte volume) = 0; - - /** - * Set the channel balance for the given handle. - * - * @param handle the sound to affect - * @param balance the new channel balance: - * (-127 ... 0 ... 127) corresponds to (left ... center ... right) - */ - virtual void setChannelBalance(SoundHandle handle, int8 balance) = 0; - - /** - * Get approximation of for how long the channel has been playing. - */ - virtual uint32 getSoundElapsedTime(SoundHandle handle) = 0; - - /** - * Get approximation of for how long the channel has been playing. - */ - virtual Timestamp getElapsedTime(SoundHandle handle) = 0; - - /** - * Check whether any channel of the given sound type is active. - * For example, this can be used to check whether any SFX sound - * is currently playing, by checking for type kSFXSoundType. - * - * @param type the sound type to look for - * @return true if any channels of the specified type are active. - */ - virtual bool hasActiveChannelOfType(SoundType type) = 0; - - /** - * Set the volume for the given sound type. - * - * @param type the sound type - * @param volume the new global volume, 0 - kMaxMixerVolume - */ - virtual void setVolumeForSoundType(SoundType type, int volume) = 0; - - /** - * Query the global volume. - * - * @param type the sound type - * @return the global music volume, 0 - kMaxMixerVolume - */ - virtual int getVolumeForSoundType(SoundType type) const = 0; - - /** - * Query the system's audio output sample rate. - * - * @return the output sample rate in Hz - */ - virtual uint getOutputRate() const = 0; -}; - - -} // End of namespace Audio - -#endif diff --git a/sound/mixer_intern.h b/sound/mixer_intern.h deleted file mode 100644 index 014be7abf2..0000000000 --- a/sound/mixer_intern.h +++ /dev/null @@ -1,135 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef SOUND_MIXER_INTERN_H -#define SOUND_MIXER_INTERN_H - -#include "common/scummsys.h" -#include "common/mutex.h" -#include "sound/mixer.h" - -namespace Audio { - -/** - * The (default) implementation of the ScummVM audio mixing subsystem. - * - * Backends are responsible for allocating (and later releasing) an instance - * of this class, which engines can access via OSystem::getMixer(). - * - * Initialisation of instances of this class usually happens as follows: - * 1) Creat a new Audio::MixerImpl instance. - * 2) Set the hardware output sample rate via the setSampleRate() method. - * 3) Hook up the mixCallback() in a suitable audio processing thread/callback. - * 4) Change the mixer into ready mode via setReady(true). - * 5) Start audio processing (e.g. by resuming the audio thread, if applicable). - * - * In the future, we might make it possible for backends to provide - * (partial) alternative implementations of the mixer, e.g. to make - * better use of native sound mixing support on low-end devices. - * - * @see OSystem::getMixer() - */ -class MixerImpl : public Mixer { -private: - enum { - NUM_CHANNELS = 16 - }; - - OSystem *_syst; - Common::Mutex _mutex; - - const uint _sampleRate; - bool _mixerReady; - uint32 _handleSeed; - - int _volumeForSoundType[4]; - Channel *_channels[NUM_CHANNELS]; - - -public: - - MixerImpl(OSystem *system, uint sampleRate); - ~MixerImpl(); - - virtual bool isReady() const { return _mixerReady; } - - virtual void playStream( - SoundType type, - SoundHandle *handle, - AudioStream *input, - int id, byte volume, int8 balance, - DisposeAfterUse::Flag autofreeStream, - bool permanent, - bool reverseStereo); - - virtual void stopAll(); - virtual void stopID(int id); - virtual void stopHandle(SoundHandle handle); - - virtual void pauseAll(bool paused); - virtual void pauseID(int id, bool paused); - virtual void pauseHandle(SoundHandle handle, bool paused); - - virtual bool isSoundIDActive(int id); - virtual int getSoundID(SoundHandle handle); - - virtual bool isSoundHandleActive(SoundHandle handle); - - virtual void setChannelVolume(SoundHandle handle, byte volume); - virtual void setChannelBalance(SoundHandle handle, int8 balance); - - virtual uint32 getSoundElapsedTime(SoundHandle handle); - virtual Timestamp getElapsedTime(SoundHandle handle); - - virtual bool hasActiveChannelOfType(SoundType type); - - virtual void setVolumeForSoundType(SoundType type, int volume); - virtual int getVolumeForSoundType(SoundType type) const; - - virtual uint getOutputRate() const; - -protected: - void insertChannel(SoundHandle *handle, Channel *chan); - -public: - /** - * The mixer callback function, to be called at regular intervals by - * the backend (e.g. from an audio mixing thread). All the actual mixing - * work is done from here. - */ - void mixCallback(byte *samples, uint len); - - /** - * Set the internal 'is ready' flag of the mixer. - * Backends should invoke Mixer::setReady(true) once initialisation of - * their audio system has been completed. - */ - void setReady(bool ready); -}; - - -} // End of namespace Audio - -#endif diff --git a/sound/mods/infogrames.cpp b/sound/mods/infogrames.cpp deleted file mode 100644 index 048872d4a0..0000000000 --- a/sound/mods/infogrames.cpp +++ /dev/null @@ -1,470 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/mods/infogrames.h" -#include "common/endian.h" -#include "common/file.h" -#include "common/memstream.h" - -namespace Audio { - -Infogrames::Instruments::Instruments() { - init(); -} - -Infogrames::Instruments::~Instruments() { - delete[] _sampleData; -} - -void Infogrames::Instruments::init() { - int i; - - for (i = 0; i < 32; i++) { - _samples[i].data = 0; - _samples[i].dataRepeat = 0; - _samples[i].length = 0; - _samples[i].lengthRepeat = 0; - } - _count = 0; - _sampleData = 0; -} - -bool Infogrames::Instruments::load(const char *ins) { - Common::File f; - - if (f.open(ins)) - return load(f); - return false; -} - -bool Infogrames::Instruments::load(Common::SeekableReadStream &ins) { - int i; - int32 fsize; - int32 offset[32]; - int32 offsetRepeat[32]; - int32 dataOffset; - - unload(); - - fsize = ins.readUint32BE(); - dataOffset = fsize; - for (i = 0; (i < 32) && !ins.eos(); i++) { - offset[i] = ins.readUint32BE(); - offsetRepeat[i] = ins.readUint32BE(); - if ((offset[i] > fsize) || (offsetRepeat[i] > fsize) || - (offset[i] < (ins.pos() + 4)) || - (offsetRepeat[i] < (ins.pos() + 4))) { - // Definitely no real entry anymore - ins.seek(-8, SEEK_CUR); - break; - } - - dataOffset = MIN(dataOffset, MIN(offset[i], offsetRepeat[i])); - ins.skip(4); // Unknown - _samples[i].length = ins.readUint16BE() * 2; - _samples[i].lengthRepeat = ins.readUint16BE() * 2; - } - - if (dataOffset >= fsize) - return false; - - _count = i; - _sampleData = new int8[fsize - dataOffset]; - ins.seek(dataOffset + 4); - ins.read(_sampleData, fsize - dataOffset); - - for (i--; i >= 0; i--) { - _samples[i].data = _sampleData + (offset[i] - dataOffset); - _samples[i].dataRepeat = _sampleData + (offsetRepeat[i] - dataOffset); - } - - return true; -} - -void Infogrames::Instruments::unload() { - delete[] _sampleData; - init(); -} - -const uint8 Infogrames::tickCount[] = - {2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96}; -const uint16 Infogrames::periods[] = - {0x6ACC, 0x64CC, 0x5F25, 0x59CE, 0x54C3, 0x5003, 0x4B86, 0x4747, 0x4346, - 0x3F8B, 0x3BF3, 0x3892, 0x3568, 0x3269, 0x2F93, 0x2CEA, 0x2A66, 0x2801, - 0x2566, 0x23A5, 0x21AF, 0x1FC4, 0x1DFE, 0x1C4E, 0x1ABC, 0x1936, 0x17CC, - 0x1676, 0x1533, 0x1401, 0x12E4, 0x11D5, 0x10D4, 0x0FE3, 0x0EFE, 0x0E26, - 0x0D5B, 0x0C9B, 0x0BE5, 0x0B3B, 0x0A9B, 0x0A02, 0x0972, 0x08E9, 0x0869, - 0x07F1, 0x077F, 0x0713, 0x06AD, 0x064D, 0x05F2, 0x059D, 0x054D, 0x0500, - 0x04B8, 0x0475, 0x0435, 0x03F8, 0x03BF, 0x038A, 0x0356, 0x0326, 0x02F9, - 0x02CF, 0x02A6, 0x0280, 0x025C, 0x023A, 0x021A, 0x01FC, 0x01E0, 0x01C5, - 0x01AB, 0x0193, 0x017D, 0x0167, 0x0153, 0x0140, 0x012E, 0x011D, 0x010D, - 0x00FE, 0x00F0, 0x00E2, 0x00D6, 0x00CA, 0x00BE, 0x00B4, 0x00AA, 0x00A0, - 0x0097, 0x008F, 0x0087, 0x007F, 0x0078, 0x0070, 0x0060, 0x0050, 0x0040, - 0x0030, 0x0020, 0x0010, 0x0000, 0x0000, 0x0020, 0x2020, 0x2020, 0x2020, - 0x2020, 0x3030, 0x3030, 0x3020, 0x2020, 0x2020, 0x2020, 0x2020, 0x2020, - 0x2020, 0x2020, 0x2020, 0x2090, 0x4040, 0x4040, 0x4040, 0x4040, 0x4040, - 0x4040, 0x4040, 0x400C, 0x0C0C, 0x0C0C, 0x0C0C, 0x0C0C, 0x0C40, 0x4040, - 0x4040, 0x4040, 0x0909, 0x0909, 0x0909, 0x0101, 0x0101, 0x0101, 0x0101, - 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x4040, 0x4040, 0x4040, - 0x0A0A, 0x0A0A, 0x0A0A, 0x0202, 0x0202, 0x0202, 0x0202, 0x0202, 0x0202, - 0x0202, 0x0202, 0x0202, 0x0202, 0x4040, 0x4040, 0x2000}; - -Infogrames::Infogrames(Instruments &ins, bool stereo, int rate, - int interruptFreq) : Paula(stereo, rate, interruptFreq) { - _instruments = &ins; - _data = 0; - _repCount = -1; - - reset(); -} - -Infogrames::~Infogrames() { - delete[] _data; -} - -void Infogrames::init() { - int i; - - _volume = 0; - _period = 0; - _sample = 0; - _speedCounter = _speed; - - for (i = 0; i < 4; i++) { - _chn[i].cmds = 0; - _chn[i].cmdBlocks = 0; - _chn[i].volSlide.finetuneNeg = 0; - _chn[i].volSlide.finetunePos = 0; - _chn[i].volSlide.data = 0; - _chn[i].volSlide.amount = 0; - _chn[i].volSlide.dataOffset = 0; - _chn[i].volSlide.flags = 0; - _chn[i].volSlide.curDelay1 = 0; - _chn[i].volSlide.curDelay2 = 0; - _chn[i].periodSlide.finetuneNeg = 0; - _chn[i].periodSlide.finetunePos = 0; - _chn[i].periodSlide.data = 0; - _chn[i].periodSlide.amount = 0; - _chn[i].periodSlide.dataOffset = 0; - _chn[i].periodSlide.flags = 0; - _chn[i].periodSlide.curDelay1 = 0; - _chn[i].periodSlide.curDelay2 = 0; - _chn[i].period = 0; - _chn[i].flags = 0x81; - _chn[i].ticks = 0; - _chn[i].tickCount = 0; - _chn[i].periodMod = 0; - } - - _end = (_data == 0); -} - -void Infogrames::reset() { - int i; - - stopPlay(); - init(); - - _volSlideBlocks = 0; - _periodSlideBlocks = 0; - _subSong = 0; - _cmdBlocks = 0; - _speedCounter = 0; - _speed = 0; - - for (i = 0; i < 4; i++) - _chn[i].cmdBlockIndices = 0; -} - -bool Infogrames::load(const char *dum) { - Common::File f; - - if (f.open(dum)) - return load(f); - return false; -} - -bool Infogrames::load(Common::SeekableReadStream &dum) { - int subSong = 0; - int i; - uint32 size; - - size = dum.size(); - if (size < 20) - return false; - - _data = new uint8[size]; - dum.seek(0); - dum.read(_data, size); - - Common::MemoryReadStream dataStr(_data, size); - - dataStr.seek(subSong * 2); - dataStr.seek(dataStr.readUint16BE()); - _subSong = _data + dataStr.pos(); - if (_subSong > (_data + size)) - return false; - - _speedCounter = dataStr.readUint16BE(); - _speed = _speedCounter; - _volSlideBlocks = _subSong + dataStr.readUint16BE(); - _periodSlideBlocks = _subSong + dataStr.readUint16BE(); - for (i = 0; i < 4; i++) { - _chn[i].cmdBlockIndices = _subSong + dataStr.readUint16BE(); - _chn[i].flags = 0x81; - } - _cmdBlocks = _data + dataStr.pos() + 2; - - if ((_volSlideBlocks > (_data + size)) || - (_periodSlideBlocks > (_data + size)) || - (_chn[0].cmdBlockIndices > (_data + size)) || - (_chn[1].cmdBlockIndices > (_data + size)) || - (_chn[2].cmdBlockIndices > (_data + size)) || - (_chn[3].cmdBlockIndices > (_data + size)) || - (_cmdBlocks > (_data + size))) - return false; - - startPaula(); - return true; -} - -void Infogrames::unload() { - stopPlay(); - - delete[] _data; - _data = 0; - - clearVoices(); - reset(); -} - -void Infogrames::getNextSample(Channel &chn) { - byte *data; - byte cmdBlock = 0; - uint16 cmd; - bool cont = false; - - if (chn.flags & 64) - return; - - if (chn.flags & 1) { - chn.flags &= ~1; - chn.cmdBlocks = chn.cmdBlockIndices; - } else { - chn.flags &= ~1; - if (_speedCounter == 0) - chn.ticks--; - if (chn.ticks != 0) { - _volume = MAX((int16) 0, tune(chn.volSlide, 0)); - _period = tune(chn.periodSlide, chn.period); - return; - } else { - chn.ticks = chn.tickCount; - cont = true; - } - } - - while (1) { - while (cont || ((cmdBlock = *chn.cmdBlocks) != 0xFF)) { - if (!cont) { - chn.cmdBlocks++; - chn.cmds = _subSong + - READ_BE_UINT16(_cmdBlocks + (cmdBlock * 2)); - } else - cont = false; - while ((cmd = *chn.cmds) != 0xFF) { - chn.cmds++; - if (cmd & 128) - { - switch (cmd & 0xE0) { - case 0x80: // 100xxxxx - Set ticks - chn.ticks = tickCount[cmd & 0xF]; - chn.tickCount = tickCount[cmd & 0xF]; - break; - case 0xA0: // 101xxxxx - Set sample - _sample = cmd & 0x1F; - break; - case 0xC0: // 110xxxxx - Set volume slide/finetune - data = _volSlideBlocks + (cmd & 0x1F) * 13; - chn.volSlide.flags = (*data & 0x80) | 1; - chn.volSlide.amount = *data++ & 0x7F; - chn.volSlide.data = data; - chn.volSlide.dataOffset = 0; - chn.volSlide.finetunePos = 0; - chn.volSlide.finetuneNeg = 0; - chn.volSlide.curDelay1 = 0; - chn.volSlide.curDelay2 = 0; - break; - case 0xE0: // 111xxxxx - Extended - switch (cmd & 0x1F) { - case 0: // Set period modifier - chn.periodMod = (int8) *chn.cmds++; - break; - case 1: // Set continuous period slide - chn.periodSlide.data = - _periodSlideBlocks + *chn.cmds++ * 13 + 1; - chn.periodSlide.amount = 0; - chn.periodSlide.dataOffset = 0; - chn.periodSlide.finetunePos = 0; - chn.periodSlide.finetuneNeg = 0; - chn.periodSlide.curDelay1 = 0; - chn.periodSlide.curDelay2 = 0; - chn.periodSlide.flags = 0x81; - break; - case 2: // Set non-continuous period slide - chn.periodSlide.data = - _periodSlideBlocks + *chn.cmds++ * 13 + 1; - chn.periodSlide.amount = 0; - chn.periodSlide.dataOffset = 0; - chn.periodSlide.finetunePos = 0; - chn.periodSlide.finetuneNeg = 0; - chn.periodSlide.curDelay1 = 0; - chn.periodSlide.curDelay2 = 0; - chn.periodSlide.flags = 1; - break; - case 3: // NOP - break; - default: - warning("Unknown Infogrames command: %X", cmd); - } - break; - } - } else { // 0xxxxxxx - Set period - if (cmd != 0) - cmd += chn.periodMod; - chn.period = periods[cmd]; - chn.volSlide.dataOffset = 0; - chn.volSlide.finetunePos = 0; - chn.volSlide.finetuneNeg = 0; - chn.volSlide.curDelay1 = 0; - chn.volSlide.curDelay2 = 0; - chn.volSlide.flags |= 1; - chn.volSlide.flags &= ~4; - chn.periodSlide.dataOffset = 0; - chn.periodSlide.finetunePos = 0; - chn.periodSlide.finetuneNeg = 0; - chn.periodSlide.curDelay1 = 0; - chn.periodSlide.curDelay2 = 0; - chn.periodSlide.flags |= 1; - chn.periodSlide.flags &= ~4; - _volume = MAX((int16) 0, tune(chn.volSlide, 0)); - _period = tune(chn.periodSlide, chn.period); - return; - } - } - } - if (!(chn.flags & 32)) { - chn.flags |= 0x40; - _volume = 0; - return; - } else - chn.cmdBlocks = chn.cmdBlockIndices; - } -} - -int16 Infogrames::tune(Slide &slide, int16 start) const { - byte *data; - uint8 off; - - data = slide.data + slide.dataOffset; - - if (slide.flags & 1) - slide.finetunePos += (int8) data[1]; - slide.flags &= ~1; - - start += slide.finetunePos - slide.finetuneNeg; - if (start < 0) - start = 0; - - if (slide.flags & 4) - return start; - - slide.curDelay1++; - if (slide.curDelay1 != data[2]) - return start; - slide.curDelay2++; - slide.curDelay1 = 0; - if (slide.curDelay2 == data[0]) { - slide.curDelay2 = 0; - off = slide.dataOffset + 3; - if (off == 12) { - if (slide.flags == 0) { - slide.flags |= 4; - return start; - } else { - slide.curDelay2 = 0; - slide.finetuneNeg += slide.amount; - off = 3; - } - } - slide.dataOffset = off; - } - slide.flags |= 1; - return start; -} - -void Infogrames::interrupt() { - int chn; - - if (!_data) { - clearVoices(); - return; - } - - _speedCounter--; - _sample = 0xFF; - for (chn = 0; chn < 4; chn++) { - _volume = 0; - _period = 0; - getNextSample(_chn[chn]); - setChannelVolume(chn, _volume); - setChannelPeriod(chn, _period); - if ((_sample != 0xFF) && (_sample < _instruments->_count)) { - setChannelData(chn, - _instruments->_samples[_sample].data, - _instruments->_samples[_sample].dataRepeat, - _instruments->_samples[_sample].length, - _instruments->_samples[_sample].lengthRepeat); - _sample = 0xFF; - } - } - if (_speedCounter == 0) - _speedCounter = _speed; - - // End reached? - if ((_chn[0].flags & 64) && (_chn[1].flags & 64) && - (_chn[2].flags & 64) && (_chn[3].flags & 64)) { - if (_repCount > 0) { - _repCount--; - init(); - } else if (_repCount != -1) { - stopPaula(); - } else { - init(); - } - } -} - -} // End of namespace Audio diff --git a/sound/mods/infogrames.h b/sound/mods/infogrames.h deleted file mode 100644 index 1f3d74f38b..0000000000 --- a/sound/mods/infogrames.h +++ /dev/null @@ -1,148 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - gob - */ - -#ifndef SOUND_MODS_INFOGRAMES_H -#define SOUND_MODS_INFOGRAMES_H - -#include "sound/mods/paula.h" -#include "common/stream.h" - -namespace Audio { - -/** A player for the Infogrames/RobHubbard2 format */ -class Infogrames : public Paula { -public: - class Instruments { - public: - Instruments(); - template Instruments(T ins) { - init(); - bool result = load(ins); - assert(result); - } - ~Instruments(); - - bool load(Common::SeekableReadStream &ins); - bool load(const char *ins); - void unload(); - - uint8 getCount() const { return _count; } - - protected: - struct Sample { - int8 *data; - int8 *dataRepeat; - uint32 length; - uint32 lengthRepeat; - } _samples[32]; - - uint8 _count; - int8 *_sampleData; - - void init(); - - friend class Infogrames; - }; - - Infogrames(Instruments &ins, bool stereo = false, int rate = 44100, - int interruptFreq = 0); - ~Infogrames(); - - Instruments *getInstruments() const { return _instruments; } - bool getRepeating() const { return _repCount != 0; } - void setRepeating (int32 repCount) { _repCount = repCount; } - - bool load(Common::SeekableReadStream &dum); - bool load(const char *dum); - void unload(); - void restart() { - if (_data) { - // Use the mutex here to ensure we do not call init() - // while data is being read by the mixer thread. - _mutex.lock(); - init(); - startPlay(); - _mutex.unlock(); - } - } - -protected: - Instruments *_instruments; - - static const uint8 tickCount[]; - static const uint16 periods[]; - byte *_data; - int32 _repCount; - - byte *_subSong; - byte *_cmdBlocks; - byte *_volSlideBlocks; - byte *_periodSlideBlocks; - uint8 _speedCounter; - uint8 _speed; - - uint16 _volume; - int16 _period; - uint8 _sample; - - struct Slide { - byte *data; - int8 amount; - uint8 dataOffset; - int16 finetuneNeg; - int16 finetunePos; - uint8 curDelay1; - uint8 curDelay2; - uint8 flags; // 0: Apply finetune modifier, 2: Don't slide, 7: Continuous - }; - struct Channel { - byte *cmdBlockIndices; - byte *cmdBlocks; - byte *cmds; - uint8 ticks; - uint8 tickCount; - Slide volSlide; - Slide periodSlide; - int16 period; - int8 periodMod; - uint8 flags; // 0: Need init, 5: Loop cmdBlocks, 6: Ignore channel - } _chn[4]; - - void init(); - void reset(); - void getNextSample(Channel &chn); - int16 tune(Slide &slide, int16 start) const; - virtual void interrupt(); -}; - -} // End of namespace Audio - -#endif diff --git a/sound/mods/maxtrax.cpp b/sound/mods/maxtrax.cpp deleted file mode 100644 index 08e73fb96a..0000000000 --- a/sound/mods/maxtrax.cpp +++ /dev/null @@ -1,1040 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "common/scummsys.h" -#include "common/endian.h" -#include "common/stream.h" -#include "common/util.h" -#include "common/debug.h" - -#include "sound/mods/maxtrax.h" - -// test for engines using this class. -#if defined(SOUND_MODS_MAXTRAX_H) - -namespace { - -enum { K_VALUE = 0x9fd77, PREF_PERIOD = 0x8fd77, PERIOD_LIMIT = 0x6f73d }; -enum { NO_BEND = 64 << 7, MAX_BEND_RANGE = 24 }; - -int32 precalcNote(byte baseNote, int16 tune, byte octave) { - return K_VALUE + 0x3C000 - ((baseNote << 14) + (tune << 11) / 3) / 3 - (octave << 16); -} - -int32 calcVolumeDelta(int32 delta, uint16 time, uint16 vBlankFreq) { - const int32 div = time * vBlankFreq; - // div <= 1000 means time to small (or even 0) - return (div <= 1000) ? delta : (1000 * delta) / div; -} - -int32 calcTempo(const uint16 tempo, uint16 vBlankFreq) { - return (int32)(((uint32)(tempo & 0xFFF0) << 8) / (uint16)(5 * vBlankFreq)); -} - -void nullFunc(int) {} - -// Function to calculate 2^x, where x is a fixedpoint number with 16 fraction bits -// using exp would be more accurate and needs less space if mathlibrary is already linked -// but this function should be faster and doesnt use floats -#if 1 -inline uint32 pow2Fixed(int32 val) { - static const uint16 tablePow2[] = { - 0, 178, 356, 535, 714, 893, 1073, 1254, 1435, 1617, 1799, 1981, 2164, 2348, 2532, 2716, - 2902, 3087, 3273, 3460, 3647, 3834, 4022, 4211, 4400, 4590, 4780, 4971, 5162, 5353, 5546, 5738, - 5932, 6125, 6320, 6514, 6710, 6906, 7102, 7299, 7496, 7694, 7893, 8092, 8292, 8492, 8693, 8894, - 9096, 9298, 9501, 9704, 9908, 10113, 10318, 10524, 10730, 10937, 11144, 11352, 11560, 11769, 11979, 12189, - 12400, 12611, 12823, 13036, 13249, 13462, 13676, 13891, 14106, 14322, 14539, 14756, 14974, 15192, 15411, 15630, - 15850, 16071, 16292, 16514, 16737, 16960, 17183, 17408, 17633, 17858, 18084, 18311, 18538, 18766, 18995, 19224, - 19454, 19684, 19915, 20147, 20379, 20612, 20846, 21080, 21315, 21550, 21786, 22023, 22260, 22498, 22737, 22977, - 23216, 23457, 23698, 23940, 24183, 24426, 24670, 24915, 25160, 25406, 25652, 25900, 26148, 26396, 26645, 26895, - 27146, 27397, 27649, 27902, 28155, 28409, 28664, 28919, 29175, 29432, 29690, 29948, 30207, 30466, 30727, 30988, - 31249, 31512, 31775, 32039, 32303, 32568, 32834, 33101, 33369, 33637, 33906, 34175, 34446, 34717, 34988, 35261, - 35534, 35808, 36083, 36359, 36635, 36912, 37190, 37468, 37747, 38028, 38308, 38590, 38872, 39155, 39439, 39724, - 40009, 40295, 40582, 40870, 41158, 41448, 41738, 42029, 42320, 42613, 42906, 43200, 43495, 43790, 44087, 44384, - 44682, 44981, 45280, 45581, 45882, 46184, 46487, 46791, 47095, 47401, 47707, 48014, 48322, 48631, 48940, 49251, - 49562, 49874, 50187, 50500, 50815, 51131, 51447, 51764, 52082, 52401, 52721, 53041, 53363, 53685, 54008, 54333, - 54658, 54983, 55310, 55638, 55966, 56296, 56626, 56957, 57289, 57622, 57956, 58291, 58627, 58964, 59301, 59640, - 59979, 60319, 60661, 61003, 61346, 61690, 62035, 62381, 62727, 63075, 63424, 63774, 64124, 64476, 64828, 65182, - 0 - }; - const uint16 whole = val >> 16; - const uint8 index = (uint8)(val >> 8); - // calculate fractional part. - const uint16 base = tablePow2[index]; - // linear interpolation and add 1.0 - uint32 exponent = ((uint32)(uint16)(tablePow2[index + 1] - base) * (uint8)val) + ((uint32)base << 8) + (1 << 24); - - if (whole < 24) { - // shift away all but the last fractional bit which is used for rounding, - // then round to nearest integer - exponent = ((exponent >> (23 - whole)) + 1) >> 1; - } else if (whole < 32) { - // no need to round here - exponent <<= whole - 24; - } else if (val > 0) { - // overflow - exponent = 0xFFFFFFFF; - } else { - // negative integer, test if >= -0.5 - exponent = (val >= -0x8000) ? 1 : 0; - } - return exponent; -} -#else -inline uint32 pow2Fixed(int32 val) { - return (uint32)(expf((float)val * (float)(0.69314718055994530942 / (1 << 16))) + 0.5f); -} -#endif - -} // End of namespace - -namespace Audio { - -MaxTrax::MaxTrax(int rate, bool stereo, uint16 vBlankFreq, uint16 maxScores) - : Paula(stereo, rate, rate / vBlankFreq), - _patch(), - _scores(), - _numScores() { - _playerCtx.maxScoreNum = maxScores; - _playerCtx.vBlankFreq = vBlankFreq; - _playerCtx.frameUnit = (uint16)((1000 << 8) / vBlankFreq); - _playerCtx.scoreIndex = -1; - _playerCtx.volume = 0x40; - - _playerCtx.tempo = 120; - _playerCtx.tempoTime = 0; - _playerCtx.filterOn = true; - _playerCtx.syncCallBack = &nullFunc; - - resetPlayer(); - for (int i = 0; i < ARRAYSIZE(_channelCtx); ++i) - _channelCtx[i].regParamNumber = 0; -} - -MaxTrax::~MaxTrax() { - stopMusic(); - freePatches(); - freeScores(); -} - -void MaxTrax::interrupt() { - // a5 - maxtraxm a4 . globaldata - - // TODO - // test for changes in shared struct and make changes - // specifically all used channels get marked altered - - _playerCtx.ticks += _playerCtx.tickUnit; - const int32 millis = _playerCtx.ticks >> 8; // d4 - - for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { - VoiceContext &voice = _voiceCtx[i]; - if (voice.stopEventTime >= 0) { - assert(voice.channel); - voice.stopEventTime -= (voice.channel < &_channelCtx[kNumChannels]) ? _playerCtx.tickUnit : _playerCtx.frameUnit; - if (voice.stopEventTime <= 0 && voice.status > VoiceContext::kStatusRelease) { - if ((voice.channel->flags & ChannelContext::kFlagDamper) != 0) - voice.hasDamper = true; - else - voice.status = VoiceContext::kStatusRelease; - } - } - } - - if (_playerCtx.scoreIndex >= 0) { - const Event *curEvent = _playerCtx.nextEvent; - int32 eventDelta = _playerCtx.nextEventTime - millis; - for (; eventDelta <= 0; eventDelta += (++curEvent)->startTime) { - const byte cmd = curEvent->command; - ChannelContext &channel = _channelCtx[curEvent->parameter & 0x0F]; - - // outPutEvent(*curEvent); - // debug("CurTime, EventDelta, NextDelta: %d, %d, %d", millis, eventDelta, eventDelta + curEvent[1].startTime ); - - if (cmd < 0x80) { // Note - const int8 voiceIndex = noteOn(channel, cmd, (curEvent->parameter & 0xF0) >> 1, kPriorityScore); - if (voiceIndex >= 0) - _voiceCtx[voiceIndex].stopEventTime = MAX(0, (eventDelta + curEvent->stopTime) << 8); - - } else { - switch (cmd) { - - case 0x80: // TEMPO - if ((_playerCtx.tickUnit >> 8) > curEvent->stopTime) { - _playerCtx.tickUnit = calcTempo(curEvent->parameter << 4, _playerCtx.vBlankFreq); - _playerCtx.tempoTime = 0; - } else { - _playerCtx.tempoStart = _playerCtx.tempo; - _playerCtx.tempoDelta = (curEvent->parameter << 4) - _playerCtx.tempoStart; - _playerCtx.tempoTime = (curEvent->stopTime << 8); - _playerCtx.tempoTicks = 0; - } - break; - - case 0xC0: // PROGRAM - channel.patch = &_patch[curEvent->stopTime & (kNumPatches - 1)]; - break; - - case 0xE0: // BEND - channel.pitchBend = ((curEvent->stopTime & 0x7F00) >> 1) | (curEvent->stopTime & 0x7f); - channel.pitchReal = (((int32)channel.pitchBendRange * channel.pitchBend) >> 5) - (channel.pitchBendRange << 8); - channel.isAltered = true; - break; - - case 0xFF: // END - if (_playerCtx.musicLoop) { - curEvent = _scores[_playerCtx.scoreIndex].events; - eventDelta = curEvent->startTime - millis; - _playerCtx.ticks = 0; - } else - _playerCtx.scoreIndex = -1; - // stop processing for this tick - goto endOfEventLoop; - - case 0xA0: // SPECIAL - switch (curEvent->stopTime >> 8){ - case 0x01: // SPECIAL_SYNC - _playerCtx.syncCallBack(curEvent->stopTime & 0xFF); - break; - case 0x02: // SPECIAL_BEGINREP - // we allow a depth of 4 loops - for (int i = 0; i < ARRAYSIZE(_playerCtx.repeatPoint); ++i) { - if (!_playerCtx.repeatPoint[i]) { - _playerCtx.repeatPoint[i] = curEvent; - _playerCtx.repeatCount[i] = curEvent->stopTime & 0xFF; - break; - } - } - break; - case 0x03: // SPECIAL_ENDREP - for (int i = ARRAYSIZE(_playerCtx.repeatPoint) - 1; i >= 0; --i) { - if (_playerCtx.repeatPoint[i]) { - if (_playerCtx.repeatCount[i]--) - curEvent = _playerCtx.repeatPoint[i]; // gets incremented by 1 at end of loop - else - _playerCtx.repeatPoint[i] = 0; - break; - } - } - break; - } - break; - - case 0xB0: // CONTROL - controlCh(channel, (byte)(curEvent->stopTime >> 8), (byte)curEvent->stopTime); - break; - - default: - debug("Unhandled Command"); - outPutEvent(*curEvent); - } - } - } -endOfEventLoop: - _playerCtx.nextEvent = curEvent; - _playerCtx.nextEventTime = eventDelta + millis; - - // tempoEffect - if (_playerCtx.tempoTime) { - _playerCtx.tempoTicks += _playerCtx.tickUnit; - uint16 newTempo = _playerCtx.tempoStart; - if (_playerCtx.tempoTicks < _playerCtx.tempoTime) { - newTempo += (uint16)((_playerCtx.tempoTicks * _playerCtx.tempoDelta) / _playerCtx.tempoTime); - } else { - _playerCtx.tempoTime = 0; - newTempo += _playerCtx.tempoDelta; - } - _playerCtx.tickUnit = calcTempo(newTempo, _playerCtx.vBlankFreq); - } - } - - // Handling of Envelopes and Portamento - for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { - VoiceContext &voice = _voiceCtx[i]; - if (!voice.channel) - continue; - const ChannelContext &channel = *voice.channel; - const Patch &patch = *voice.patch; - - switch (voice.status) { - case VoiceContext::kStatusSustain: - // we need to check if some voices have no sustainSample. - // in that case they are finished after the attackSample is done - if (voice.dmaOff && Paula::getChannelDmaCount((byte)i) >= voice.dmaOff ) { - voice.dmaOff = 0; - voice.isBlocked = 0; - voice.priority = 0; - // disable it in next tick - voice.stopEventTime = 0; - } - if (!channel.isAltered && !voice.hasPortamento && !channel.modulation) - continue; - // Update Volume and Period - break; - - case VoiceContext::kStatusHalt: - killVoice((byte)i); - continue; - - case VoiceContext::kStatusStart: - if (patch.attackLen) { - voice.envelope = patch.attackPtr; - const uint16 duration = voice.envelope->duration; - voice.envelopeLeft = patch.attackLen; - voice.ticksLeft = duration << 8; - voice.status = VoiceContext::kStatusAttack; - voice.incrVolume = calcVolumeDelta((int32)voice.envelope->volume, duration, _playerCtx.vBlankFreq); - // Process Envelope - } else { - voice.status = VoiceContext::kStatusSustain; - voice.baseVolume = patch.volume; - // Update Volume and Period - } - break; - - case VoiceContext::kStatusRelease: - if (patch.releaseLen) { - voice.envelope = patch.attackPtr + patch.attackLen; - const uint16 duration = voice.envelope->duration; - voice.envelopeLeft = patch.releaseLen; - voice.ticksLeft = duration << 8; - voice.status = VoiceContext::kStatusDecay; - voice.incrVolume = calcVolumeDelta((int32)voice.envelope->volume - voice.baseVolume, duration, _playerCtx.vBlankFreq); - // Process Envelope - } else { - voice.status = VoiceContext::kStatusHalt; - voice.lastVolume = 0; - // Send Audio Packet - } - voice.stopEventTime = -1; - break; - } - - // Process Envelope - const uint16 envUnit = _playerCtx.frameUnit; - if (voice.envelope) { - if (voice.ticksLeft > envUnit) { // envelope still active - voice.baseVolume = (uint16) MIN(MAX(0, voice.baseVolume + voice.incrVolume), 0x8000); - voice.ticksLeft -= envUnit; - // Update Volume and Period - - } else { // next or last Envelope - voice.baseVolume = voice.envelope->volume; - assert(voice.envelopeLeft > 0); - if (--voice.envelopeLeft) { - ++voice.envelope; - const uint16 duration = voice.envelope->duration; - voice.ticksLeft = duration << 8; - voice.incrVolume = calcVolumeDelta((int32)voice.envelope->volume - voice.baseVolume, duration, _playerCtx.vBlankFreq); - // Update Volume and Period - } else if (voice.status == VoiceContext::kStatusDecay) { - voice.status = VoiceContext::kStatusHalt; - voice.envelope = 0; - voice.lastVolume = 0; - // Send Audio Packet - } else { - assert(voice.status == VoiceContext::kStatusAttack); - voice.status = VoiceContext::kStatusSustain; - voice.envelope = 0; - // Update Volume and Period - } - } - } - - // Update Volume and Period - if (voice.status >= VoiceContext::kStatusDecay) { - // Calc volume - uint16 vol = (voice.noteVolume < (1 << 7)) ? (voice.noteVolume * _playerCtx.volume) >> 7 : _playerCtx.volume; - if (voice.baseVolume < (1 << 15)) - vol = (uint16)(((uint32)vol * voice.baseVolume) >> 15); - if (voice.channel->volume < (1 << 7)) - vol = (vol * voice.channel->volume) >> 7; - voice.lastVolume = (byte)MIN(vol, (uint16)0x64); - - // Calc Period - if (voice.hasPortamento) { - voice.portaTicks += envUnit; - if ((uint16)(voice.portaTicks >> 8) >= channel.portamentoTime) { - voice.hasPortamento = false; - voice.baseNote = voice.endNote; - voice.preCalcNote = precalcNote(voice.baseNote, patch.tune, voice.octave); - } - voice.lastPeriod = calcNote(voice); - } else if (channel.isAltered || channel.modulation) - voice.lastPeriod = calcNote(voice); - } - - // Send Audio Packet - Paula::setChannelPeriod((byte)i, (voice.lastPeriod) ? voice.lastPeriod : 1000); - Paula::setChannelVolume((byte)i, (voice.lastPeriod) ? voice.lastVolume : 0); - } - for (ChannelContext *c = _channelCtx; c != &_channelCtx[ARRAYSIZE(_channelCtx)]; ++c) - c->isAltered = false; - -#ifdef MAXTRAX_HAS_MODULATION - // original player had _playerCtx.sineValue = _playerCtx.frameUnit >> 2 - // this should fit the comments that modtime=1000 is one second ? - _playerCtx.sineValue += _playerCtx.frameUnit; -#endif -} - -void MaxTrax::controlCh(ChannelContext &channel, const byte command, const byte data) { - switch (command) { - case 0x01: // modulation level MSB - channel.modulation = data << 8; - break; - case 0x21: // modulation level LSB - channel.modulation = (channel.modulation & 0xFF00) || ((data * 2) & 0xFF); - break; - case 0x05: // portamento time MSB - channel.portamentoTime = data << 7; - break; - case 0x25: // portamento time LSB - channel.portamentoTime = (channel.portamentoTime & 0x3f80) || data; - break; - case 0x06: // data entry MSB - if (channel.regParamNumber == 0) { - channel.pitchBendRange = (int8)MIN((uint8)MAX_BEND_RANGE, (uint8)data); - channel.pitchReal = (((int32)channel.pitchBendRange * channel.pitchBend) >> 5) - (channel.pitchBendRange << 8); - channel.isAltered = true; - } - break; - case 0x07: // Main Volume MSB - channel.volume = (data == 0) ? 0 : data + 1; - channel.isAltered = true; - break; - case 0x0A: // Pan - if (data > 0x40 || (data == 0x40 && ((&channel - _channelCtx) & 1) != 0)) - channel.flags |= ChannelContext::kFlagRightChannel; - else - channel.flags &= ~ChannelContext::kFlagRightChannel; - break; - case 0x10: // GPC as Modulation Time MSB - channel.modulationTime = data << 7; - break; - case 0x30: // GPC as Modulation Time LSB - channel.modulationTime = (channel.modulationTime & 0x3f80) || data; - break; - case 0x11: // GPC as Microtonal Set MSB - channel.microtonal = data << 8; - break; - case 0x31: // GPC as Microtonal Set LSB - channel.microtonal = (channel.microtonal & 0xFF00) || ((data * 2) & 0xFF); - break; - case 0x40: // Damper Pedal - if ((data & 0x40) != 0) - channel.flags |= ChannelContext::kFlagDamper; - else { - channel.flags &= ~ChannelContext::kFlagDamper; - // release all dampered voices on this channel - for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { - if (_voiceCtx[i].channel == &channel && _voiceCtx[i].hasDamper) { - _voiceCtx[i].hasDamper = false; - _voiceCtx[i].status = VoiceContext::kStatusRelease; - } - } - } - break; - case 0x41: // Portamento off/on - if ((data & 0x40) != 0) - channel.flags |= ChannelContext::kFlagPortamento; - else - channel.flags &= ~ChannelContext::kFlagPortamento; - break; - case 0x50: // Microtonal off/on - if ((data & 0x40) != 0) - channel.flags |= ChannelContext::kFlagMicrotonal; - else - channel.flags &= ~ChannelContext::kFlagMicrotonal; - break; - case 0x51: // Audio Filter off/on - Paula::setAudioFilter(data > 0x40 || (data == 0x40 && _playerCtx.filterOn)); - break; - case 0x65: // RPN MSB - channel.regParamNumber = (data << 8) || (channel.regParamNumber & 0xFF); - break; - case 0x64: // RPN LSB - channel.regParamNumber = (channel.regParamNumber & 0xFF00) || data; - break; - case 0x79: // Reset All Controllers - resetChannel(channel, ((&channel - _channelCtx) & 1) != 0); - break; - case 0x7E: // MONO mode - channel.flags |= ChannelContext::kFlagMono; - goto allNotesOff; - case 0x7F: // POLY mode - channel.flags &= ~ChannelContext::kFlagMono; - // Fallthrough - case 0x7B: // All Notes Off -allNotesOff: - for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { - if (_voiceCtx[i].channel == &channel) { - if ((channel.flags & ChannelContext::kFlagDamper) != 0) - _voiceCtx[i].hasDamper = true; - else - _voiceCtx[i].status = VoiceContext::kStatusRelease; - } - } - break; - case 0x78: // All Sounds Off - for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { - if (_voiceCtx[i].channel == &channel) - killVoice((byte)i); - } - break; - } -} - -void MaxTrax::setTempo(const uint16 tempo) { - Common::StackLock lock(_mutex); - _playerCtx.tickUnit = calcTempo(tempo, _playerCtx.vBlankFreq); -} - -void MaxTrax::resetPlayer() { - for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) - killVoice((byte)i); - - for (int i = 0; i < ARRAYSIZE(_channelCtx); ++i) { - _channelCtx[i].flags = 0; - _channelCtx[i].lastNote = (uint8)-1; - resetChannel(_channelCtx[i], (i & 1) != 0); - _channelCtx[i].patch = (i < kNumChannels) ? &_patch[i] : 0; - } - -#ifdef MAXTRAX_HAS_MICROTONAL - for (int i = 0; i < ARRAYSIZE(_microtonal); ++i) - _microtonal[i] = (int16)(i << 8); -#endif -} - -void MaxTrax::stopMusic() { - Common::StackLock lock(_mutex); - _playerCtx.scoreIndex = -1; - for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { - if (_voiceCtx[i].channel < &_channelCtx[kNumChannels]) - killVoice((byte)i); - } -} - -bool MaxTrax::playSong(int songIndex, bool loop) { - if (songIndex < 0 || songIndex >= _numScores) - return false; - Common::StackLock lock(_mutex); - _playerCtx.scoreIndex = -1; - resetPlayer(); - for (int i = 0; i < ARRAYSIZE(_playerCtx.repeatPoint); ++i) - _playerCtx.repeatPoint[i] = 0; - - setTempo(_playerCtx.tempoInitial << 4); - Paula::setAudioFilter(_playerCtx.filterOn); - _playerCtx.musicLoop = loop; - _playerCtx.tempoTime = 0; - _playerCtx.scoreIndex = songIndex; - _playerCtx.ticks = 0; - - _playerCtx.nextEvent = _scores[songIndex].events; - _playerCtx.nextEventTime = _playerCtx.nextEvent->startTime; - - Paula::startPaula(); - return true; -} - -void MaxTrax::advanceSong(int advance) { - Common::StackLock lock(_mutex); - if (_playerCtx.scoreIndex >= 0) { - const Event *cev = _playerCtx.nextEvent; - if (cev) { - for (; advance > 0; --advance) { - // TODO - check for boundaries - for (; cev->command != 0xFF && (cev->command != 0xA0 || (cev->stopTime >> 8) != 0x00); ++cev) - ; // no end_command or special_command + end - } - _playerCtx.nextEvent = cev; - } - } -} - -void MaxTrax::killVoice(byte num) { - VoiceContext &voice = _voiceCtx[num]; - voice.channel = 0; - voice.envelope = 0; - voice.status = VoiceContext::kStatusFree; - voice.isBlocked = 0; - voice.hasDamper = false; - voice.hasPortamento = false; - voice.priority = 0; - voice.stopEventTime = -1; - voice.dmaOff = 0; - voice.lastVolume = 0; - voice.tieBreak = 0; - //voice.uinqueId = 0; - - // "stop" voice, set period to 1, vol to 0 - Paula::disableChannel(num); - Paula::setChannelPeriod(num, 1); - Paula::setChannelVolume(num, 0); -} - -int8 MaxTrax::pickvoice(uint pick, int16 pri) { - enum { kPrioFlagFixedSide = 1 << 3 }; - pick &= 3; - if ((pri & (kPrioFlagFixedSide)) == 0) { - const bool leftSide = (uint)(pick - 1) > 1; - const int leftBest = MIN(_voiceCtx[0].status, _voiceCtx[3].status); - const int rightBest = MIN(_voiceCtx[1].status, _voiceCtx[2].status); - const int sameSide = (leftSide) ? leftBest : rightBest; - const int otherSide = leftBest + rightBest - sameSide; - - if (sameSide > VoiceContext::kStatusRelease && otherSide <= VoiceContext::kStatusRelease) - pick ^= 1; // switches sides - } - pri &= ~kPrioFlagFixedSide; - - for (int i = 2; i > 0; --i) { - VoiceContext *voice = &_voiceCtx[pick]; - VoiceContext *alternate = &_voiceCtx[pick ^ 3]; - - const uint16 voiceVal = voice->status << 8 | voice->lastVolume; - const uint16 altVal = alternate->status << 8 | alternate->lastVolume; - - if (voiceVal + voice->tieBreak > altVal - || voice->isBlocked > alternate->isBlocked) { - - // this is somewhat different to the original player, - // but has a similar result - voice->tieBreak = 0; - alternate->tieBreak = 1; - - pick ^= 3; // switch channels - VoiceContext *tmp = voice; - voice = alternate; - alternate = tmp; - } - - if (voice->isBlocked || voice->priority > pri) { - // if not already done, switch sides and try again - pick ^= 1; - continue; - } - // succeded - return (int8)pick; - } - // failed - debug(5, "MaxTrax: could not find channel for note"); - return -1; -} - -uint16 MaxTrax::calcNote(const VoiceContext &voice) { - const ChannelContext &channel = *voice.channel; - int16 bend = channel.pitchReal; - -#ifdef MAXTRAX_HAS_MICROTONAL - if (voice.hasPortamento) { - if ((channel.flags & ChannelContext::kFlagMicrotonal) != 0) - bend += (int16)(((_microtonal[voice.endNote] - _microtonal[voice.baseNote]) * voice.portaTicks) >> 8) / channel.portamentoTime; - else - bend += (int16)(((int8)(voice.endNote - voice.baseNote)) * voice.portaTicks) / channel.portamentoTime; - } - - if ((channel.flags & ChannelContext::kFlagMicrotonal) != 0) - bend += _microtonal[voice.baseNote]; -#else - if (voice.hasPortamento) - bend += (int16)(((int8)(voice.endNote - voice.baseNote)) * voice.portaTicks) / channel.portamentoTime; -#endif - -#ifdef MAXTRAX_HAS_MODULATION - static const uint8 tableSine[] = { - 0, 5, 12, 18, 24, 30, 37, 43, 49, 55, 61, 67, 73, 79, 85, 91, - 97, 103, 108, 114, 120, 125, 131, 136, 141, 146, 151, 156, 161, 166, 171, 176, - 180, 184, 189, 193, 197, 201, 205, 208, 212, 215, 219, 222, 225, 228, 230, 233, - 236, 238, 240, 242, 244, 246, 247, 249, 250, 251, 252, 253, 254, 254, 255, 255, - 255, 255, 255, 254, 254, 253, 252, 251, 250, 249, 247, 246, 244, 242, 240, 238, - 236, 233, 230, 228, 225, 222, 219, 215, 212, 208, 205, 201, 197, 193, 189, 184, - 180, 176, 171, 166, 161, 156, 151, 146, 141, 136, 131, 125, 120, 114, 108, 103, - 97, 91, 85, 79, 73, 67, 61, 55, 49, 43, 37, 30, 24, 18, 12, 5 - }; - if (channel.modulation) { - if ((channel.flags & ChannelContext::kFlagModVolume) == 0) { - const uint8 sineByte = _playerCtx.sineValue / channel.modulationTime; - const uint8 sineIndex = sineByte & 0x7F; - const int16 modVal = ((uint32)(uint16)(tableSine[sineIndex] + (sineIndex ? 1 : 0)) * channel.modulation) >> 8; - bend = (sineByte < 0x80) ? bend + modVal : bend - modVal; - } - } -#endif - - // tone = voice.baseNote << 8 + microtonal - // bend = channelPitch + porta + modulation - - const int32 tone = voice.preCalcNote + (bend << 6) / 3; - - return (tone >= PERIOD_LIMIT) ? (uint16)pow2Fixed(tone) : 0; -} - -int8 MaxTrax::noteOn(ChannelContext &channel, const byte note, uint16 volume, uint16 pri) { -#ifdef MAXTRAX_HAS_MICROTONAL - if (channel.microtonal >= 0) - _microtonal[note % 127] = channel.microtonal; -#endif - - if (!volume) - return -1; - - const Patch &patch = *channel.patch; - if (!patch.samplePtr || patch.sampleTotalLen == 0) - return -1; - int8 voiceNum = -1; - if ((channel.flags & ChannelContext::kFlagMono) == 0) { - voiceNum = pickvoice((channel.flags & ChannelContext::kFlagRightChannel) != 0 ? 1 : 0, pri); - } else { - VoiceContext *voice = _voiceCtx + ARRAYSIZE(_voiceCtx) - 1; - for (voiceNum = ARRAYSIZE(_voiceCtx) - 1; voiceNum >= 0 && voice->channel != &channel; --voiceNum, --voice) - ; - if (voiceNum < 0) - voiceNum = pickvoice((channel.flags & ChannelContext::kFlagRightChannel) != 0 ? 1 : 0, pri); - else if (voice->status >= VoiceContext::kStatusSustain && (channel.flags & ChannelContext::kFlagPortamento) != 0) { - // reset previous porta - if (voice->hasPortamento) - voice->baseNote = voice->endNote; - voice->preCalcNote = precalcNote(voice->baseNote, patch.tune, voice->octave); - voice->noteVolume = (_playerCtx.handleVolume) ? volume + 1 : 128; - voice->portaTicks = 0; - voice->hasPortamento = true; - voice->endNote = channel.lastNote = note; - return voiceNum; - } - } - - if (voiceNum >= 0) { - VoiceContext &voice = _voiceCtx[voiceNum]; - voice.hasDamper = false; - voice.isBlocked = 0; - voice.hasPortamento = false; - if (voice.channel) - killVoice(voiceNum); - voice.channel = &channel; - voice.patch = &patch; - voice.baseNote = note; - - // always base octave on the note in the command, regardless of porta - const int32 plainNote = precalcNote(note, patch.tune, 0); - // calculate which sample to use - const int useOctave = (plainNote <= PREF_PERIOD) ? 0 : MIN((plainNote + 0xFFFF - PREF_PERIOD) >> 16, patch.sampleOctaves - 1); - voice.octave = (byte)useOctave; - // adjust precalculated value - voice.preCalcNote = plainNote - (useOctave << 16); - - // next calculate the actual period which depends on wether porta is enabled - if (&channel < &_channelCtx[kNumChannels] && (channel.flags & ChannelContext::kFlagPortamento) != 0) { - if ((channel.flags & ChannelContext::kFlagMono) != 0 && channel.lastNote < 0x80 && channel.lastNote != note) { - voice.portaTicks = 0; - voice.baseNote = channel.lastNote; - voice.endNote = note; - voice.hasPortamento = true; - voice.preCalcNote = precalcNote(voice.baseNote, patch.tune, voice.octave); - } - channel.lastNote = note; - } - - voice.lastPeriod = calcNote(voice); - - voice.priority = (byte)pri; - voice.status = VoiceContext::kStatusStart; - - voice.noteVolume = (_playerCtx.handleVolume) ? volume + 1 : 128; - voice.baseVolume = 0; - - // TODO: since the original player is using the OS-functions, more than 1 sample could be queued up already - // get samplestart for the given octave - const int8 *samplePtr = patch.samplePtr + (patch.sampleTotalLen << useOctave) - patch.sampleTotalLen; - if (patch.sampleAttackLen) { - Paula::setChannelSampleStart(voiceNum, samplePtr); - Paula::setChannelSampleLen(voiceNum, (patch.sampleAttackLen << useOctave) / 2); - - Paula::enableChannel(voiceNum); - // wait for dma-clear - } - - if (patch.sampleTotalLen > patch.sampleAttackLen) { - Paula::setChannelSampleStart(voiceNum, samplePtr + (patch.sampleAttackLen << useOctave)); - Paula::setChannelSampleLen(voiceNum, ((patch.sampleTotalLen - patch.sampleAttackLen) << useOctave) / 2); - if (!patch.sampleAttackLen) - Paula::enableChannel(voiceNum); // need to enable channel - // another pointless wait for DMA-Clear??? - - } else { // no sustain sample - // this means we must stop playback after the attacksample finished - // so we queue up an "empty" sample and note that we need to kill the sample after dma finished - Paula::setChannelSampleStart(voiceNum, 0); - Paula::setChannelSampleLen(voiceNum, 0); - Paula::setChannelDmaCount(voiceNum); - voice.dmaOff = 1; - } - - Paula::setChannelPeriod(voiceNum, (voice.lastPeriod) ? voice.lastPeriod : 1000); - Paula::setChannelVolume(voiceNum, 0); - } - return voiceNum; -} - -void MaxTrax::resetChannel(ChannelContext &chan, bool rightChannel) { - chan.modulation = 0; - chan.modulationTime = 1000; - chan.microtonal = -1; - chan.portamentoTime = 500; - chan.pitchBend = NO_BEND; - chan.pitchReal = 0; - chan.pitchBendRange = MAX_BEND_RANGE; - chan.volume = 128; - chan.flags &= ~(ChannelContext::kFlagPortamento | ChannelContext::kFlagMicrotonal | ChannelContext::kFlagRightChannel); - chan.isAltered = true; - if (rightChannel) - chan.flags |= ChannelContext::kFlagRightChannel; -} - -void MaxTrax::freeScores() { - if (_scores) { - for (int i = 0; i < _numScores; ++i) - delete[] _scores[i].events; - delete[] _scores; - _scores = 0; - } - _numScores = 0; - _playerCtx.tempo = 120; - _playerCtx.filterOn = true; -} - -void MaxTrax::freePatches() { - for (int i = 0; i < ARRAYSIZE(_patch); ++i) { - delete[] _patch[i].samplePtr; - delete[] _patch[i].attackPtr; - } - memset(_patch, 0, sizeof(_patch)); -} - -void MaxTrax::setSignalCallback(void (*callback) (int)) { - Common::StackLock lock(_mutex); - _playerCtx.syncCallBack = (callback == 0) ? nullFunc : callback; -} - -int MaxTrax::playNote(byte note, byte patch, uint16 duration, uint16 volume, bool rightSide) { - Common::StackLock lock(_mutex); - assert(patch < ARRAYSIZE(_patch)); - - ChannelContext &channel = _channelCtx[kNumChannels]; - channel.flags = (rightSide) ? ChannelContext::kFlagRightChannel : 0; - channel.isAltered = false; - channel.patch = &_patch[patch]; - const int8 voiceIndex = noteOn(channel, note, (byte)volume, kPriorityNote); - if (voiceIndex >= 0) { - _voiceCtx[voiceIndex].stopEventTime = duration << 8; - Paula::startPaula(); - } - return voiceIndex; -} - -bool MaxTrax::load(Common::SeekableReadStream &musicData, bool loadScores, bool loadSamples) { - Common::StackLock lock(_mutex); - stopMusic(); - if (loadSamples) - freePatches(); - if (loadScores) - freeScores(); - const char *errorMsg = 0; - // 0x0000: 4 Bytes Header "MXTX" - // 0x0004: uint16 tempo - // 0x0006: uint16 flags. bit0 = lowpassfilter, bit1 = attackvolume, bit15 = microtonal - if (musicData.size() < 10 || musicData.readUint32BE() != 0x4D585458) { - warning("Maxtrax: File is not a Maxtrax Module"); - return false; - } - const uint16 songTempo = musicData.readUint16BE(); - const uint16 flags = musicData.readUint16BE(); - if (loadScores) { - _playerCtx.tempoInitial = songTempo; - _playerCtx.filterOn = (flags & 1) != 0; - _playerCtx.handleVolume = (flags & 2) != 0; - } - - if (flags & (1 << 15)) { - debug(5, "Maxtrax: Song has microtonal"); -#ifdef MAXTRAX_HAS_MICROTONAL - if (loadScores) { - for (int i = 0; i < ARRAYSIZE(_microtonal); ++i) - _microtonal[i] = musicData.readUint16BE(); - } else - musicData.skip(128 * 2); -#else - musicData.skip(128 * 2); -#endif - } - - int scoresLoaded = 0; - // uint16 number of Scores - const uint16 scoresInFile = musicData.readUint16BE(); - - if (musicData.err() || musicData.eos()) - goto ioError; - - if (loadScores) { - const uint16 tempScores = MIN(scoresInFile, _playerCtx.maxScoreNum); - Score *curScore = new Score[tempScores]; - if (!curScore) - goto allocError; - _scores = curScore; - - for (scoresLoaded = 0; scoresLoaded < tempScores; ++scoresLoaded, ++curScore) { - const uint32 numEvents = musicData.readUint32BE(); - Event *curEvent = new Event[numEvents]; - if (!curEvent) - goto allocError; - curScore->events = curEvent; - for (int j = numEvents; j > 0; --j, ++curEvent) { - curEvent->command = musicData.readByte(); - curEvent->parameter = musicData.readByte(); - curEvent->startTime = musicData.readUint16BE(); - curEvent->stopTime = musicData.readUint16BE(); - } - curScore->numEvents = numEvents; - } - _numScores = scoresLoaded; - } - - if (loadSamples) { - // skip over remaining scores in file - for (int i = scoresInFile - scoresLoaded; i > 0; --i) - musicData.skip(musicData.readUint32BE() * 6); - - // uint16 number of Samples - const uint16 wavesInFile = musicData.readUint16BE(); - for (int i = wavesInFile; i > 0; --i) { - // load disksample structure - const uint16 number = musicData.readUint16BE(); - assert(number < ARRAYSIZE(_patch)); - - Patch &curPatch = _patch[number]; - if (curPatch.attackPtr || curPatch.samplePtr) { - delete curPatch.attackPtr; - curPatch.attackPtr = 0; - delete curPatch.samplePtr; - curPatch.samplePtr = 0; - } - curPatch.tune = musicData.readSint16BE(); - curPatch.volume = musicData.readUint16BE(); - curPatch.sampleOctaves = musicData.readUint16BE(); - curPatch.sampleAttackLen = musicData.readUint32BE(); - const uint32 sustainLen = musicData.readUint32BE(); - curPatch.sampleTotalLen = curPatch.sampleAttackLen + sustainLen; - // each octave the number of samples doubles. - const uint32 totalSamples = curPatch.sampleTotalLen * ((1 << curPatch.sampleOctaves) - 1); - curPatch.attackLen = musicData.readUint16BE(); - curPatch.releaseLen = musicData.readUint16BE(); - const uint32 totalEnvs = curPatch.attackLen + curPatch.releaseLen; - - // Allocate space for both attack and release Segment. - Envelope *envPtr = new Envelope[totalEnvs]; - if (!envPtr) - goto allocError; - // Attack Segment - curPatch.attackPtr = envPtr; - // Release Segment - // curPatch.releasePtr = envPtr + curPatch.attackLen; - - // Read Attack and Release Segments - for (int j = totalEnvs; j > 0; --j, ++envPtr) { - envPtr->duration = musicData.readUint16BE(); - envPtr->volume = musicData.readUint16BE(); - } - - // read Samples - int8 *allocSamples = new int8[totalSamples]; - if (!allocSamples) - goto allocError; - curPatch.samplePtr = allocSamples; - musicData.read(allocSamples, totalSamples); - } - } - if (!musicData.err() && !musicData.eos()) - return true; -ioError: - errorMsg = "Maxtrax: Encountered IO-Error"; -allocError: - if (!errorMsg) - errorMsg = "Maxtrax: Could not allocate Memory"; - - warning("%s", errorMsg); - if (loadSamples) - freePatches(); - if (loadScores) - freeScores(); - return false; -} - -#if !defined(NDEBUG) && 0 -void MaxTrax::outPutEvent(const Event &ev, int num) { - struct { - byte cmd; - const char *name; - const char *param; - } COMMANDS[] = { - {0x80, "TEMPO ", "TEMPO, N/A "}, - {0xa0, "SPECIAL ", "CHAN, SPEC # | VAL"}, - {0xb0, "CONTROL ", "CHAN, CTRL # | VAL"}, - {0xc0, "PROGRAM ", "CHANNEL, PROG # "}, - {0xe0, "BEND ", "CHANNEL, BEND VALUE"}, - {0xf0, "SYSEX ", "TYPE, SIZE "}, - {0xf8, "REALTIME", "REALTIME, N/A "}, - {0xff, "END ", "N/A, N/A "}, - {0xff, "NOTE ", "VOL | CHAN, STOP"}, - }; - - int i = 0; - for (; i < ARRAYSIZE(COMMANDS) - 1 && ev.command != COMMANDS[i].cmd; ++i) - ; - - if (num == -1) - debug("Event : %02X %s %s %02X %04X %04X", ev.command, COMMANDS[i].name, COMMANDS[i].param, ev.parameter, ev.startTime, ev.stopTime); - else - debug("Event %3d: %02X %s %s %02X %04X %04X", num, ev.command, COMMANDS[i].name, COMMANDS[i].param, ev.parameter, ev.startTime, ev.stopTime); -} - -void MaxTrax::outPutScore(const Score &sc, int num) { - if (num == -1) - debug("score : %i Events", sc.numEvents); - else - debug("score %2d: %i Events", num, sc.numEvents); - for (uint i = 0; i < sc.numEvents; ++i) - outPutEvent(sc.events[i], i); - debug(""); -} -#else -void MaxTrax::outPutEvent(const Event &ev, int num) {} -void MaxTrax::outPutScore(const Score &sc, int num) {} -#endif // #ifndef NDEBUG - -} // End of namespace Audio - -#endif // #if defined(SOUND_MODS_MAXTRAX_H) diff --git a/sound/mods/maxtrax.h b/sound/mods/maxtrax.h deleted file mode 100644 index 2c86b70288..0000000000 --- a/sound/mods/maxtrax.h +++ /dev/null @@ -1,225 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -// see if all engines using this class are DISABLED -#if !defined(ENABLE_KYRA) - -// normal Header Guard -#elif !defined SOUND_MODS_MAXTRAX_H -#define SOUND_MODS_MAXTRAX_H - -// #define MAXTRAX_HAS_MODULATION -// #define MAXTRAX_HAS_MICROTONAL - -#include "sound/mods/paula.h" - -namespace Audio { - -class MaxTrax : public Paula { -public: - MaxTrax(int rate, bool stereo, uint16 vBlankFreq = 50, uint16 maxScores = 128); - virtual ~MaxTrax(); - - bool load(Common::SeekableReadStream &musicData, bool loadScores = true, bool loadSamples = true); - bool playSong(int songIndex, bool loop = false); - void advanceSong(int advance = 1); - int playNote(byte note, byte patch, uint16 duration, uint16 volume, bool rightSide); - void setVolume(const byte volume) { Common::StackLock lock(_mutex); _playerCtx.volume = volume; } - void setTempo(const uint16 tempo); - void stopMusic(); - /** - * Set a callback function for sync-events. - * @param callback Callback function, will be called synchronously, so DONT modify the player - * directly in response - */ - void setSignalCallback(void (*callback) (int)); - -protected: - void interrupt(); - -private: - enum { kNumPatches = 64, kNumVoices = 4, kNumChannels = 16, kNumExtraChannels = 1 }; - enum { kPriorityScore, kPriorityNote, kPrioritySound }; - -#ifdef MAXTRAX_HAS_MICROTONAL - int16 _microtonal[128]; -#endif - - struct Event { - uint16 startTime; - uint16 stopTime; - byte command; - byte parameter; - }; - - const struct Score { - const Event *events; - uint32 numEvents; - } *_scores; - - int _numScores; - - struct { - uint32 sineValue; - uint16 vBlankFreq; - int32 ticks; - int32 tickUnit; - uint16 frameUnit; - - uint16 maxScoreNum; - uint16 tempo; - uint16 tempoInitial; - uint16 tempoStart; - int16 tempoDelta; - int32 tempoTime; - int32 tempoTicks; - - byte volume; - - bool filterOn; - bool handleVolume; - bool musicLoop; - - int scoreIndex; - const Event *nextEvent; - int32 nextEventTime; - - void (*syncCallBack) (int); - const Event *repeatPoint[4]; - byte repeatCount[4]; - } _playerCtx; - - struct Envelope { - uint16 duration; - uint16 volume; - }; - - struct Patch { - const Envelope *attackPtr; - //Envelope *releasePtr; - uint16 attackLen; - uint16 releaseLen; - - int16 tune; - uint16 volume; - - // this was the SampleData struct in the assembler source - const int8 *samplePtr; - uint32 sampleTotalLen; - uint32 sampleAttackLen; - uint16 sampleOctaves; - } _patch[kNumPatches]; - - struct ChannelContext { - const Patch *patch; - uint16 regParamNumber; - - uint16 modulation; - uint16 modulationTime; - - int16 microtonal; - - uint16 portamentoTime; - - int16 pitchBend; - int16 pitchReal; - int8 pitchBendRange; - - uint8 volume; -// uint8 voicesActive; - - enum { - kFlagRightChannel = 1 << 0, - kFlagPortamento = 1 << 1, - kFlagDamper = 1 << 2, - kFlagMono = 1 << 3, - kFlagMicrotonal = 1 << 4, - kFlagModVolume = 1 << 5 - }; - byte flags; - bool isAltered; - - uint8 lastNote; -// uint8 program; - - } _channelCtx[kNumChannels + kNumExtraChannels]; - - struct VoiceContext { - ChannelContext *channel; - const Patch *patch; - const Envelope *envelope; -// uint32 uinqueId; - int32 preCalcNote; - uint32 ticksLeft; - int32 portaTicks; - int32 incrVolume; -// int32 periodOffset; - uint16 envelopeLeft; - uint16 noteVolume; - uint16 baseVolume; - uint16 lastPeriod; - byte baseNote; - byte endNote; - byte octave; -// byte number; -// byte link; - enum { - kStatusFree, - kStatusHalt, - kStatusDecay, - kStatusRelease, - kStatusSustain, - kStatusAttack, - kStatusStart - }; - uint8 isBlocked; - uint8 priority; - byte status; - byte lastVolume; - byte tieBreak; - bool hasDamper; - bool hasPortamento; - byte dmaOff; - - int32 stopEventTime; - } _voiceCtx[kNumVoices]; - - void controlCh(ChannelContext &channel, byte command, byte data); - void freePatches(); - void freeScores(); - void resetChannel(ChannelContext &chan, bool rightChannel); - void resetPlayer(); - - int8 pickvoice(uint pick, int16 pri); - uint16 calcNote(const VoiceContext &voice); - int8 noteOn(ChannelContext &channel, byte note, uint16 volume, uint16 pri); - void killVoice(byte num); - - static void outPutEvent(const Event &ev, int num = -1); - static void outPutScore(const Score &sc, int num = -1); -}; -} // End of namespace Audio - -#endif // !defined SOUND_MODS_MAXTRAX_H diff --git a/sound/mods/module.cpp b/sound/mods/module.cpp deleted file mode 100644 index 992c2a28e3..0000000000 --- a/sound/mods/module.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/mods/module.h" - -#include "common/util.h" -#include "common/endian.h" - -namespace Modules { - -const int16 Module::periods[16][60] = { - {1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960 , 906, - 856 , 808 , 762 , 720 , 678 , 640 , 604 , 570 , 538 , 508 , 480 , 453, - 428 , 404 , 381 , 360 , 339 , 320 , 302 , 285 , 269 , 254 , 240 , 226, - 214 , 202 , 190 , 180 , 170 , 160 , 151 , 143 , 135 , 127 , 120 , 113, - 107 , 101 , 95 , 90 , 85 , 80 , 75 , 71 , 67 , 63 , 60 , 56 }, - {1700, 1604, 1514, 1430, 1348, 1274, 1202, 1134, 1070, 1010, 954 , 900, - 850 , 802 , 757 , 715 , 674 , 637 , 601 , 567 , 535 , 505 , 477 , 450, - 425 , 401 , 379 , 357 , 337 , 318 , 300 , 284 , 268 , 253 , 239 , 225, - 213 , 201 , 189 , 179 , 169 , 159 , 150 , 142 , 134 , 126 , 119 , 113, - 106 , 100 , 94 , 89 , 84 , 79 , 75 , 71 , 67 , 63 , 59 , 56 }, - {1688, 1592, 1504, 1418, 1340, 1264, 1194, 1126, 1064, 1004, 948 , 894, - 844 , 796 , 752 , 709 , 670 , 632 , 597 , 563 , 532 , 502 , 474 , 447, - 422 , 398 , 376 , 355 , 335 , 316 , 298 , 282 , 266 , 251 , 237 , 224, - 211 , 199 , 188 , 177 , 167 , 158 , 149 , 141 , 133 , 125 , 118 , 112, - 105 , 99 , 94 , 88 , 83 , 79 , 74 , 70 , 66 , 62 , 59 , 56 }, - {1676, 1582, 1492, 1408, 1330, 1256, 1184, 1118, 1056, 996 , 940 , 888, - 838 , 791 , 746 , 704 , 665 , 628 , 592 , 559 , 528 , 498 , 470 , 444, - 419 , 395 , 373 , 352 , 332 , 314 , 296 , 280 , 264 , 249 , 235 , 222, - 209 , 198 , 187 , 176 , 166 , 157 , 148 , 140 , 132 , 125 , 118 , 111, - 104 , 99 , 93 , 88 , 83 , 78 , 74 , 70 , 66 , 62 , 59 , 55 }, - {1664, 1570, 1482, 1398, 1320, 1246, 1176, 1110, 1048, 990 , 934 , 882, - 832 , 785 , 741 , 699 , 660 , 623 , 588 , 555 , 524 , 495 , 467 , 441, - 416 , 392 , 370 , 350 , 330 , 312 , 294 , 278 , 262 , 247 , 233 , 220, - 208 , 196 , 185 , 175 , 165 , 156 , 147 , 139 , 131 , 124 , 117 , 110, - 104 , 98 , 92 , 87 , 82 , 78 , 73 , 69 , 65 , 62 , 58 , 55 }, - {1652, 1558, 1472, 1388, 1310, 1238, 1168, 1102, 1040, 982 , 926 , 874, - 826 , 779 , 736 , 694 , 655 , 619 , 584 , 551 , 520 , 491 , 463 , 437, - 413 , 390 , 368 , 347 , 328 , 309 , 292 , 276 , 260 , 245 , 232 , 219, - 206 , 195 , 184 , 174 , 164 , 155 , 146 , 138 , 130 , 123 , 116 , 109, - 103 , 97 , 92 , 87 , 82 , 77 , 73 , 69 , 65 , 61 , 58 , 54 }, - {1640, 1548, 1460, 1378, 1302, 1228, 1160, 1094, 1032, 974 , 920 , 868, - 820 , 774 , 730 , 689 , 651 , 614 , 580 , 547 , 516 , 487 , 460 , 434, - 410 , 387 , 365 , 345 , 325 , 307 , 290 , 274 , 258 , 244 , 230 , 217, - 205 , 193 , 183 , 172 , 163 , 154 , 145 , 137 , 129 , 122 , 115 , 109, - 102 , 96 , 91 , 86 , 81 , 77 , 72 , 68 , 64 , 61 , 57 , 54 }, - {1628, 1536, 1450, 1368, 1292, 1220, 1150, 1086, 1026, 968 , 914 , 862, - 814 , 768 , 725 , 684 , 646 , 610 , 575 , 543 , 513 , 484 , 457 , 431, - 407 , 384 , 363 , 342 , 323 , 305 , 288 , 272 , 256 , 242 , 228 , 216, - 204 , 192 , 181 , 171 , 161 , 152 , 144 , 136 , 128 , 121 , 114 , 108, - 102 , 96 , 90 , 85 , 80 , 76 , 72 , 68 , 64 , 60 , 57 , 54 }, - {1814, 1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960, - 907 , 856 , 808 , 762 , 720 , 678 , 640 , 604 , 570 , 538 , 508 , 480, - 453 , 428 , 404 , 381 , 360 , 339 , 320 , 302 , 285 , 269 , 254 , 240, - 226 , 214 , 202 , 190 , 180 , 170 , 160 , 151 , 143 , 135 , 127 , 120, - 113 , 107 , 101 , 95 , 90 , 85 , 80 , 75 , 71 , 67 , 63 , 60 }, - {1800, 1700, 1604, 1514, 1430, 1350, 1272, 1202, 1134, 1070, 1010, 954, - 900 , 850 , 802 , 757 , 715 , 675 , 636 , 601 , 567 , 535 , 505 , 477, - 450 , 425 , 401 , 379 , 357 , 337 , 318 , 300 , 284 , 268 , 253 , 238, - 225 , 212 , 200 , 189 , 179 , 169 , 159 , 150 , 142 , 134 , 126 , 119, - 112 , 106 , 100 , 94 , 89 , 84 , 79 , 75 , 71 , 67 , 63 , 59 }, - {1788, 1688, 1592, 1504, 1418, 1340, 1264, 1194, 1126, 1064, 1004, 948, - 894 , 844 , 796 , 752 , 709 , 670 , 632 , 597 , 563 , 532 , 502 , 474, - 447 , 422 , 398 , 376 , 355 , 335 , 316 , 298 , 282 , 266 , 251 , 237, - 223 , 211 , 199 , 188 , 177 , 167 , 158 , 149 , 141 , 133 , 125 , 118, - 111 , 105 , 99 , 94 , 88 , 83 , 79 , 74 , 70 , 66 , 62 , 59 }, - {1774, 1676, 1582, 1492, 1408, 1330, 1256, 1184, 1118, 1056, 996 , 940, - 887 , 838 , 791 , 746 , 704 , 665 , 628 , 592 , 559 , 528 , 498 , 470, - 444 , 419 , 395 , 373 , 352 , 332 , 314 , 296 , 280 , 264 , 249 , 235, - 222 , 209 , 198 , 187 , 176 , 166 , 157 , 148 , 140 , 132 , 125 , 118, - 111 , 104 , 99 , 93 , 88 , 83 , 78 , 74 , 70 , 66 , 62 , 59 }, - {1762, 1664, 1570, 1482, 1398, 1320, 1246, 1176, 1110, 1048, 988 , 934, - 881 , 832 , 785 , 741 , 699 , 660 , 623 , 588 , 555 , 524 , 494 , 467, - 441 , 416 , 392 , 370 , 350 , 330 , 312 , 294 , 278 , 262 , 247 , 233, - 220 , 208 , 196 , 185 , 175 , 165 , 156 , 147 , 139 , 131 , 123 , 117, - 110 , 104 , 98 , 92 , 87 , 82 , 78 , 73 , 69 , 65 , 61 , 58 }, - {1750, 1652, 1558, 1472, 1388, 1310, 1238, 1168, 1102, 1040, 982 , 926, - 875 , 826 , 779 , 736 , 694 , 655 , 619 , 584 , 551 , 520 , 491 , 463, - 437 , 413 , 390 , 368 , 347 , 328 , 309 , 292 , 276 , 260 , 245 , 232, - 219 , 206 , 195 , 184 , 174 , 164 , 155 , 146 , 138 , 130 , 123 , 116, - 109 , 103 , 97 , 92 , 87 , 82 , 77 , 73 , 69 , 65 , 61 , 58 }, - {1736, 1640, 1548, 1460, 1378, 1302, 1228, 1160, 1094, 1032, 974 , 920, - 868 , 820 , 774 , 730 , 689 , 651 , 614 , 580 , 547 , 516 , 487 , 460, - 434 , 410 , 387 , 365 , 345 , 325 , 307 , 290 , 274 , 258 , 244 , 230, - 217 , 205 , 193 , 183 , 172 , 163 , 154 , 145 , 137 , 129 , 122 , 115, - 108 , 102 , 96 , 91 , 86 , 81 , 77 , 72 , 68 , 64 , 61 , 57 }, - {1724, 1628, 1536, 1450, 1368, 1292, 1220, 1150, 1086, 1026, 968 , 914, - 862 , 814 , 768 , 725 , 684 , 646 , 610 , 575 , 543 , 513 , 484 , 457, - 431 , 407 , 384 , 363 , 342 , 323 , 305 , 288 , 272 , 256 , 242 , 228, - 216 , 203 , 192 , 181 , 171 , 161 , 152 , 144 , 136 , 128 , 121 , 114, - 108 , 101 , 96 , 90 , 85 , 80 , 76 , 72 , 68 , 64 , 60 , 57 }}; - -const uint32 Module::signatures[] = { - MKID_BE('M.K.'), MKID_BE('M!K!'), MKID_BE('FLT4') -}; - -bool Module::load(Common::SeekableReadStream &st, int offs) { - if (offs) { - // Load the module with the common sample data - load(st, 0); - } - - st.seek(offs); - st.read(songname, 20); - songname[20] = '\0'; - - for (int i = 0; i < NUM_SAMPLES; ++i) { - st.read(sample[i].name, 22); - sample[i].name[22] = '\0'; - sample[i].len = 2 * st.readUint16BE(); - - sample[i].finetune = st.readByte(); - assert(sample[i].finetune < 0x10); - - sample[i].vol = st.readByte(); - sample[i].repeat = 2 * st.readUint16BE(); - sample[i].replen = 2 * st.readUint16BE(); - } - - songlen = st.readByte(); - undef = st.readByte(); - - st.read(songpos, 128); - - sig = st.readUint32BE(); - - bool foundSig = false; - for (int i = 0; i < ARRAYSIZE(signatures); i++) { - if (sig == signatures[i]) { - foundSig = true; - break; - } - } - - if (!foundSig) { - warning("No known signature found in protracker module"); - return false; - } - - int maxpattern = 0; - for (int i = 0; i < 128; ++i) - if (maxpattern < songpos[i]) - maxpattern = songpos[i]; - - pattern = new pattern_t[maxpattern + 1]; - - for (int i = 0; i <= maxpattern; ++i) { - for (int j = 0; j < 64; ++j) { - for (int k = 0; k < 4; ++k) { - uint32 note = st.readUint32BE(); - pattern[i][j][k].sample = (note & 0xf0000000) >> 24 | (note & 0x0000f000) >> 12; - pattern[i][j][k].period = (note >> 16) & 0xfff; - pattern[i][j][k].effect = note & 0xfff; - pattern[i][j][k].note = periodToNote((note >> 16) & 0xfff); - } - } - } - - for (int i = 0; i < NUM_SAMPLES; ++i) { - if (offs) { - // Restore information for modules that use common sample data - for (int j = 0; j < NUM_SAMPLES; ++j) { - if (!scumm_stricmp((const char *)commonSamples[j].name, (const char *)sample[i].name)) { - sample[i].len = commonSamples[j].len; - st.seek(commonSamples[j].offs); - break; - } - } - } else { - // Store information for modules that use common sample data - memcpy(commonSamples[i].name, sample[i].name, 22); - commonSamples[i].len = sample[i].len; - commonSamples[i].offs = st.pos(); - - } - - if (!sample[i].len) { - sample[i].data = 0; - } else { - sample[i].data = new int8[sample[i].len]; - st.read((byte *)sample[i].data, sample[i].len); - } - } - - return true; -} - -Module::Module() { - pattern = 0; - for (int i = 0; i < NUM_SAMPLES; ++i) { - sample[i].data = 0; - } -} - -Module::~Module() { - delete[] pattern; - for (int i = 0; i < NUM_SAMPLES; ++i) { - delete[] sample[i].data; - } -} - -byte Module::periodToNote(int16 period, byte finetune) { - int16 diff1; - int16 diff2; - - diff1 = ABS(periods[finetune][0] - period); - if (diff1 == 0) - return 0; - - for (int i = 1; i < 60; i++) { - diff2 = ABS(periods[finetune][i] - period); - if (diff2 == 0) - return i; - else if (diff2 > diff1) - return i-1; - diff1 = diff2; - } - return 59; -} - -int16 Module::noteToPeriod(byte note, byte finetune) { - if (finetune > 15) - finetune = 15; - if (note > 59) - note = 59; - - return periods[finetune][note]; -} - -} // End of namespace Modules diff --git a/sound/mods/module.h b/sound/mods/module.h deleted file mode 100644 index 550b63617e..0000000000 --- a/sound/mods/module.h +++ /dev/null @@ -1,90 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef SOUND_MODS_MODULE_H -#define SOUND_MODS_MODULE_H - -#include "common/stream.h" - -namespace Modules { - -#include "common/pack-start.h" // START STRUCT PACKING - -struct note_t { - byte sample; - byte note; - uint16 period; - uint16 effect; -} PACKED_STRUCT; - -#include "common/pack-end.h" // END STRUCT PACKING - -typedef note_t pattern_t[64][4]; - -struct sample_t { - byte name[23]; - uint16 len; - byte finetune; - byte vol; - uint16 repeat; - uint16 replen; - int8 *data; -}; - -struct sample_offs { - byte name[23]; - uint16 len; - uint32 offs; -}; - -class Module { -public: - byte songname[21]; - - static const int NUM_SAMPLES = 31; - sample_t sample[NUM_SAMPLES]; - sample_offs commonSamples[NUM_SAMPLES]; - - byte songlen; - byte undef; - byte songpos[128]; - uint32 sig; - pattern_t *pattern; - - Module(); - ~Module(); - - bool load(Common::SeekableReadStream &stream, int offs); - static byte periodToNote(int16 period, byte finetune = 0); - static int16 noteToPeriod(byte note, byte finetune = 0); - -private: - static const int16 periods[16][60]; - static const uint32 signatures[]; -}; - -} // End of namespace Modules - -#endif diff --git a/sound/mods/paula.cpp b/sound/mods/paula.cpp deleted file mode 100644 index c39b37f83d..0000000000 --- a/sound/mods/paula.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/mods/paula.h" -#include "sound/null.h" - -namespace Audio { - -Paula::Paula(bool stereo, int rate, uint interruptFreq) : - _stereo(stereo), _rate(rate), _periodScale((double)kPalPaulaClock / rate), _intFreq(interruptFreq) { - - clearVoices(); - _voice[0].panning = 191; - _voice[1].panning = 63; - _voice[2].panning = 63; - _voice[3].panning = 191; - - if (_intFreq == 0) - _intFreq = _rate; - - _curInt = 0; - _timerBase = 1; - _playing = false; - _end = true; -} - -Paula::~Paula() { -} - -void Paula::clearVoice(byte voice) { - assert(voice < NUM_VOICES); - - _voice[voice].data = 0; - _voice[voice].dataRepeat = 0; - _voice[voice].length = 0; - _voice[voice].lengthRepeat = 0; - _voice[voice].period = 0; - _voice[voice].volume = 0; - _voice[voice].offset = Offset(0); - _voice[voice].dmaCount = 0; -} - -int Paula::readBuffer(int16 *buffer, const int numSamples) { - Common::StackLock lock(_mutex); - - memset(buffer, 0, numSamples * 2); - if (!_playing) { - return numSamples; - } - - if (_stereo) - return readBufferIntern(buffer, numSamples); - else - return readBufferIntern(buffer, numSamples); -} - - -template -inline int mixBuffer(int16 *&buf, const int8 *data, Paula::Offset &offset, frac_t rate, int neededSamples, uint bufSize, byte volume, byte panning) { - int samples; - for (samples = 0; samples < neededSamples && offset.int_off < bufSize; ++samples) { - const int32 tmp = ((int32) data[offset.int_off]) * volume; - if (stereo) { - *buf++ += (tmp * (255 - panning)) >> 7; - *buf++ += (tmp * (panning)) >> 7; - } else - *buf++ += tmp; - - // Step to next source sample - offset.rem_off += rate; - if (offset.rem_off >= (frac_t)FRAC_ONE) { - offset.int_off += fracToInt(offset.rem_off); - offset.rem_off &= FRAC_LO_MASK; - } - } - - return samples; -} - -template -int Paula::readBufferIntern(int16 *buffer, const int numSamples) { - int samples = _stereo ? numSamples / 2 : numSamples; - while (samples > 0) { - - // Handle 'interrupts'. This gives subclasses the chance to adjust the channel data - // (e.g. insert new samples, do pitch bending, whatever). - if (_curInt == 0) { - _curInt = _intFreq; - interrupt(); - } - - // Compute how many samples to generate: at most the requested number of samples, - // of course, but we may stop earlier when an 'interrupt' is expected. - const uint nSamples = MIN((uint)samples, _curInt); - - // Loop over the four channels of the emulated Paula chip - for (int voice = 0; voice < NUM_VOICES; voice++) { - // No data, or paused -> skip channel - if (!_voice[voice].data || (_voice[voice].period <= 0)) - continue; - - // The Paula chip apparently run at 7.0937892 MHz in the PAL - // version and at 7.1590905 MHz in the NTSC version. We divide this - // by the requested the requested output sampling rate _rate - // (typically 44.1 kHz or 22.05 kHz) obtaining the value _periodScale. - // This is then divided by the "period" of the channel we are - // processing, to obtain the correct output 'rate'. - frac_t rate = doubleToFrac(_periodScale / _voice[voice].period); - // Cap the volume - _voice[voice].volume = MIN((byte) 0x40, _voice[voice].volume); - - - Channel &ch = _voice[voice]; - int16 *p = buffer; - int neededSamples = nSamples; - assert(ch.offset.int_off < ch.length); - - // Mix the generated samples into the output buffer - neededSamples -= mixBuffer(p, ch.data, ch.offset, rate, neededSamples, ch.length, ch.volume, ch.panning); - - // Wrap around if necessary - if (ch.offset.int_off >= ch.length) { - // Important: Wrap around the offset *before* updating the voice length. - // Otherwise, if length != lengthRepeat we would wrap incorrectly. - // Note: If offset >= 2*len ever occurs, the following would be wrong; - // instead of subtracting, we then should compute the modulus using "%=". - // Since that requires a division and is slow, and shouldn't be necessary - // in practice anyway, we only use subtraction. - ch.offset.int_off -= ch.length; - ch.dmaCount++; - - ch.data = ch.dataRepeat; - ch.length = ch.lengthRepeat; - } - - // If we have not yet generated enough samples, and looping is active: loop! - if (neededSamples > 0 && ch.length > 2) { - // Repeat as long as necessary. - while (neededSamples > 0) { - // Mix the generated samples into the output buffer - neededSamples -= mixBuffer(p, ch.data, ch.offset, rate, neededSamples, ch.length, ch.volume, ch.panning); - - if (ch.offset.int_off >= ch.length) { - // Wrap around. See also the note above. - ch.offset.int_off -= ch.length; - ch.dmaCount++; - } - } - } - - } - buffer += _stereo ? nSamples * 2 : nSamples; - _curInt -= nSamples; - samples -= nSamples; - } - return numSamples; -} - -} // End of namespace Audio - - -// Plugin interface -// (This can only create a null driver since apple II gs support seeems not to be implemented -// and also is not part of the midi driver architecture. But we need the plugin for the options -// menu in the launcher and for MidiDriver::detectDevice() which is more or less used by all engines.) - -class AmigaMusicPlugin : public NullMusicPlugin { -public: - const char *getName() const { - return _s("Amiga Audio Emulator"); - } - - const char *getId() const { - return "amiga"; - } - - MusicDevices getDevices() const; -}; - -MusicDevices AmigaMusicPlugin::getDevices() const { - MusicDevices devices; - devices.push_back(MusicDevice(this, "", MT_AMIGA)); - return devices; -} - -//#if PLUGIN_ENABLED_DYNAMIC(AMIGA) - //REGISTER_PLUGIN_DYNAMIC(AMIGA, PLUGIN_TYPE_MUSIC, AmigaMusicPlugin); -//#else - REGISTER_PLUGIN_STATIC(AMIGA, PLUGIN_TYPE_MUSIC, AmigaMusicPlugin); -//#endif diff --git a/sound/mods/paula.h b/sound/mods/paula.h deleted file mode 100644 index aa3d5b4ab9..0000000000 --- a/sound/mods/paula.h +++ /dev/null @@ -1,210 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef SOUND_MODS_PAULA_H -#define SOUND_MODS_PAULA_H - -#include "sound/audiostream.h" -#include "common/frac.h" -#include "common/mutex.h" - -namespace Audio { - -/** - * Emulation of the "Paula" Amiga music chip - * The interrupt frequency specifies the number of mixed wavesamples between - * calls of the interrupt method - */ -class Paula : public AudioStream { -public: - static const int NUM_VOICES = 4; - enum { - kPalSystemClock = 7093790, - kNtscSystemClock = 7159090, - kPalCiaClock = kPalSystemClock / 10, - kNtscCiaClock = kNtscSystemClock / 10, - kPalPaulaClock = kPalSystemClock / 2, - kNtscPauleClock = kNtscSystemClock / 2 - }; - - /* TODO: Document this */ - struct Offset { - uint int_off; // integral part of the offset - frac_t rem_off; // fractional part of the offset, at least 0 and less than 1 - - explicit Offset(int off = 0) : int_off(off), rem_off(0) {} - }; - - Paula(bool stereo = false, int rate = 44100, uint interruptFreq = 0); - ~Paula(); - - bool playing() const { return _playing; } - void setTimerBaseValue( uint32 ticksPerSecond ) { _timerBase = ticksPerSecond; } - uint32 getTimerBaseValue() { return _timerBase; } - void setSingleInterrupt(uint sampleDelay) { assert(sampleDelay < _intFreq); _curInt = sampleDelay; } - void setSingleInterruptUnscaled(uint timerDelay) { - setSingleInterrupt((uint)(((double)timerDelay * getRate()) / _timerBase)); - } - void setInterruptFreq(uint sampleDelay) { _intFreq = sampleDelay; _curInt = 0; } - void setInterruptFreqUnscaled(uint timerDelay) { - setInterruptFreq((uint)(((double)timerDelay * getRate()) / _timerBase)); - } - void clearVoice(byte voice); - void clearVoices() { for (int i = 0; i < NUM_VOICES; ++i) clearVoice(i); } - void startPlay() { _playing = true; } - void stopPlay() { _playing = false; } - void pausePlay(bool pause) { _playing = !pause; } - -// AudioStream API - int readBuffer(int16 *buffer, const int numSamples); - bool isStereo() const { return _stereo; } - bool endOfData() const { return _end; } - int getRate() const { return _rate; } - -protected: - struct Channel { - const int8 *data; - const int8 *dataRepeat; - uint32 length; - uint32 lengthRepeat; - int16 period; - byte volume; - Offset offset; - byte panning; // For stereo mixing: 0 = far left, 255 = far right - int dmaCount; - }; - - bool _end; - Common::Mutex _mutex; - - virtual void interrupt() = 0; - - void startPaula() { - _playing = true; - _end = false; - } - - void stopPaula() { - _playing = false; - _end = true; - } - - void setChannelPanning(byte channel, byte panning) { - assert(channel < NUM_VOICES); - _voice[channel].panning = panning; - } - - void disableChannel(byte channel) { - assert(channel < NUM_VOICES); - _voice[channel].data = 0; - } - - void enableChannel(byte channel) { - assert(channel < NUM_VOICES); - Channel &ch = _voice[channel]; - ch.data = ch.dataRepeat; - ch.length = ch.lengthRepeat; - // actually first 2 bytes are dropped? - ch.offset = Offset(0); - // ch.period = ch.periodRepeat; - } - - void setChannelPeriod(byte channel, int16 period) { - assert(channel < NUM_VOICES); - _voice[channel].period = period; - } - - void setChannelVolume(byte channel, byte volume) { - assert(channel < NUM_VOICES); - _voice[channel].volume = volume; - } - - void setChannelSampleStart(byte channel, const int8 *data) { - assert(channel < NUM_VOICES); - _voice[channel].dataRepeat = data; - } - - void setChannelSampleLen(byte channel, uint32 length) { - assert(channel < NUM_VOICES); - assert(length < 32768/2); - _voice[channel].lengthRepeat = 2 * length; - } - - void setChannelData(uint8 channel, const int8 *data, const int8 *dataRepeat, uint32 length, uint32 lengthRepeat, int32 offset = 0) { - assert(channel < NUM_VOICES); - - Channel &ch = _voice[channel]; - - ch.dataRepeat = data; - ch.lengthRepeat = length; - enableChannel(channel); - ch.offset = Offset(offset); - - ch.dataRepeat = dataRepeat; - ch.lengthRepeat = lengthRepeat; - } - - void setChannelOffset(byte channel, Offset offset) { - assert(channel < NUM_VOICES); - _voice[channel].offset = offset; - } - - Offset getChannelOffset(byte channel) { - assert(channel < NUM_VOICES); - return _voice[channel].offset; - } - - int getChannelDmaCount(byte channel) { - assert(channel < NUM_VOICES); - return _voice[channel].dmaCount; - } - - void setChannelDmaCount(byte channel, int dmaVal = 0) { - assert(channel < NUM_VOICES); - _voice[channel].dmaCount = dmaVal; - } - - void setAudioFilter(bool enable) { - // TODO: implement - } - -private: - Channel _voice[NUM_VOICES]; - - const bool _stereo; - const int _rate; - const double _periodScale; - uint _intFreq; - uint _curInt; - uint32 _timerBase; - bool _playing; - - template - int readBufferIntern(int16 *buffer, const int numSamples); -}; - -} // End of namespace Audio - -#endif diff --git a/sound/mods/protracker.cpp b/sound/mods/protracker.cpp deleted file mode 100644 index 797b4c417d..0000000000 --- a/sound/mods/protracker.cpp +++ /dev/null @@ -1,466 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/mods/protracker.h" -#include "sound/mods/paula.h" -#include "sound/mods/module.h" - -#include "sound/audiostream.h" - -namespace Modules { - -class ProtrackerStream : public ::Audio::Paula { -private: - Module _module; - - int _tick; - int _row; - int _pos; - - int _speed; - int _bpm; - - // For effect 0xB - Jump To Pattern; - bool _hasJumpToPattern; - int _jumpToPattern; - - // For effect 0xD - PatternBreak; - bool _hasPatternBreak; - int _skipRow; - - // For effect 0xE6 - Pattern Loop - bool _hasPatternLoop; - int _patternLoopCount; - int _patternLoopRow; - - // For effect 0xEE - Pattern Delay - byte _patternDelay; - - static const int16 sinetable[]; - - struct { - byte sample; - uint16 period; - Offset offset; - - byte vol; - byte finetune; - - // For effect 0x0 - Arpeggio - bool arpeggio; - byte arpeggioNotes[3]; - - // For effect 0x3 - Porta to note - uint16 portaToNote; - byte portaToNoteSpeed; - - // For effect 0x4 - Vibrato - int vibrato; - byte vibratoPos; - byte vibratoSpeed; - byte vibratoDepth; - - // For effect 0xED - Delay sample - byte delaySample; - byte delaySampleTick; - } _track[4]; - -public: - ProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo); - -private: - void interrupt(); - - void doPorta(int track) { - if (_track[track].portaToNote && _track[track].portaToNoteSpeed) { - if (_track[track].period < _track[track].portaToNote) { - _track[track].period += _track[track].portaToNoteSpeed; - if (_track[track].period > _track[track].portaToNote) - _track[track].period = _track[track].portaToNote; - } else if (_track[track].period > _track[track].portaToNote) { - _track[track].period -= _track[track].portaToNoteSpeed; - if (_track[track].period < _track[track].portaToNote) - _track[track].period = _track[track].portaToNote; - } - } - } - void doVibrato(int track) { - _track[track].vibrato = - (_track[track].vibratoDepth * sinetable[_track[track].vibratoPos]) / 128; - _track[track].vibratoPos += _track[track].vibratoSpeed; - _track[track].vibratoPos %= 64; - } - void doVolSlide(int track, byte ex, byte ey) { - int vol = _track[track].vol; - if (ex == 0) - vol -= ey; - else if (ey == 0) - vol += ex; - - if (vol < 0) - vol = 0; - else if (vol > 64) - vol = 64; - - _track[track].vol = vol; - } - - void updateRow(); - void updateEffects(); - -}; - -const int16 ProtrackerStream::sinetable[64] = { - 0, 24, 49, 74, 97, 120, 141, 161, - 180, 197, 212, 224, 235, 244, 250, 253, - 255, 253, 250, 244, 235, 224, 212, 197, - 180, 161, 141, 120, 97, 74, 49, 24, - 0, -24, -49, -74, -97, -120, -141, -161, - -180, -197, -212, -224, -235, -244, -250, -253, - -255, -253, -250, -244, -235, -224, -212, -197, - -180, -161, -141, -120, -97, -74, -49, -24 -}; - -ProtrackerStream::ProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo) : - Paula(stereo, rate, rate/50) { - bool result = _module.load(*stream, offs); - assert(result); - - _tick = _row = _pos = 0; - - _speed = 6; - _bpm = 125; - - _hasJumpToPattern = false; - _jumpToPattern = 0; - - _hasPatternBreak = false; - _skipRow = 0; - - _hasPatternLoop = false; - _patternLoopCount = 0; - _patternLoopRow = 0; - - _patternDelay = 0; - - memset(_track, 0, sizeof(_track)); - - startPaula(); -} - -void ProtrackerStream::updateRow() { - for (int track = 0; track < 4; track++) { - _track[track].arpeggio = false; - _track[track].vibrato = 0; - _track[track].delaySampleTick = 0; - const note_t note = - _module.pattern[_module.songpos[_pos]][_row][track]; - - const int effect = note.effect >> 8; - - if (note.sample) { - if (_track[track].sample != note.sample) { - _track[track].vibratoPos = 0; - } - _track[track].sample = note.sample; - _track[track].finetune = _module.sample[note.sample - 1].finetune; - _track[track].vol = _module.sample[note.sample - 1].vol; - } - - if (note.period) { - if (effect != 3 && effect != 5) { - if (_track[track].finetune) - _track[track].period = _module.noteToPeriod(note.note, _track[track].finetune); - else - _track[track].period = note.period; - _track[track].offset = Offset(0); - } - } - - const byte exy = note.effect & 0xff; - const byte ex = (note.effect >> 4) & 0xf; - const byte ey = note.effect & 0xf; - - int vol; - switch (effect) { - case 0x0: - if (exy) { - _track[track].arpeggio = true; - if (note.period) { - _track[track].arpeggioNotes[0] = note.note; - _track[track].arpeggioNotes[1] = note.note + ex; - _track[track].arpeggioNotes[2] = note.note + ey; - } - } - break; - case 0x1: - break; - case 0x2: - break; - case 0x3: - if (note.period) - _track[track].portaToNote = note.period; - if (exy) - _track[track].portaToNoteSpeed = exy; - break; - case 0x4: - if (exy) { - _track[track].vibratoSpeed = ex; - _track[track].vibratoDepth = ey; - } - break; - case 0x5: - doPorta(track); - doVolSlide(track, ex, ey); - break; - case 0x6: - doVibrato(track); - doVolSlide(track, ex, ey); - break; - case 0x9: // Set sample offset - if (exy) { - _track[track].offset = Offset(exy * 256); - setChannelOffset(track, _track[track].offset); - } - break; - case 0xA: - break; - case 0xB: - _hasJumpToPattern = true; - _jumpToPattern = exy; - break; - case 0xC: - _track[track].vol = exy; - break; - case 0xD: - _hasPatternBreak = true; - _skipRow = ex * 10 + ey; - break; - case 0xE: - switch (ex) { - case 0x0: // Switch filters off - break; - case 0x1: // Fine slide up - _track[track].period -= exy; - break; - case 0x2: // Fine slide down - _track[track].period += exy; - break; - case 0x5: // Set finetune - _track[track].finetune = ey; - _module.sample[_track[track].sample].finetune = ey; - if (note.period) { - if (ey) - _track[track].period = _module.noteToPeriod(note.note, ey); - else - _track[track].period = note.period; - } - break; - case 0x6: - if (ey == 0) { - _patternLoopRow = _row; - } else { - _patternLoopCount++; - if (_patternLoopCount <= ey) - _hasPatternLoop = true; - else - _patternLoopCount = 0; - } - break; - case 0x9: - break; // Retrigger note - case 0xA: // Fine volume slide up - vol = _track[track].vol + ey; - if (vol > 64) - vol = 64; - _track[track].vol = vol; - break; - case 0xB: // Fine volume slide down - vol = _track[track].vol - ey; - if (vol < 0) - vol = 0; - _track[track].vol = vol; - break; - case 0xD: // Delay sample - _track[track].delaySampleTick = ey; - _track[track].delaySample = _track[track].sample; - _track[track].sample = 0; - _track[track].vol = 0; - break; - case 0xE: // Pattern delay - _patternDelay = ey; - break; - default: - warning("Unimplemented effect %X", note.effect); - } - break; - - case 0xF: - if (exy < 0x20) { - _speed = exy; - } else { - _bpm = exy; - setInterruptFreq((int) (getRate() / (_bpm * 0.4))); - } - break; - default: - warning("Unimplemented effect %X", note.effect); - } - } -} - -void ProtrackerStream::updateEffects() { - for (int track = 0; track < 4; track++) { - _track[track].vibrato = 0; - - const note_t note = - _module.pattern[_module.songpos[_pos]][_row][track]; - - const int effect = note.effect >> 8; - - const int exy = note.effect & 0xff; - const int ex = (note.effect >> 4) & 0xf; - const int ey = (note.effect) & 0xf; - - switch (effect) { - case 0x0: - if (exy) { - const int idx = (_tick == 1) ? 0 : (_tick % 3); - _track[track].period = - _module.noteToPeriod(_track[track].arpeggioNotes[idx], - _track[track].finetune); - } - break; - case 0x1: - _track[track].period -= exy; - break; - case 0x2: - _track[track].period += exy; - break; - case 0x3: - doPorta(track); - break; - case 0x4: - doVibrato(track); - break; - case 0x5: - doPorta(track); - doVolSlide(track, ex, ey); - break; - case 0x6: - doVibrato(track); - doVolSlide(track, ex, ey); - break; - case 0xA: - doVolSlide(track, ex, ey); - break; - case 0xE: - switch (ex) { - case 0x6: - break; // Pattern loop - case 0x9: // Retrigger note - if (ey && (_tick % ey) == 0) - _track[track].offset = Offset(0); - break; - case 0xD: // Delay sample - if (_tick == _track[track].delaySampleTick) { - _track[track].sample = _track[track].delaySample; - _track[track].offset = Offset(0); - if (_track[track].sample) - _track[track].vol = _module.sample[_track[track].sample - 1].vol; - } - break; - } - break; - } - } -} - -void ProtrackerStream::interrupt() { - int track; - - for (track = 0; track < 4; track++) { - _track[track].offset = getChannelOffset(track); - if (_tick == 0 && _track[track].arpeggio) { - _track[track].period = _module.noteToPeriod(_track[track].arpeggioNotes[0], - _track[track].finetune); - } - } - - if (_tick == 0) { - if (_hasJumpToPattern) { - _hasJumpToPattern = false; - _pos = _jumpToPattern; - _row = 0; - } else if (_hasPatternBreak) { - _hasPatternBreak = false; - _row = _skipRow; - _pos = (_pos + 1) % _module.songlen; - _patternLoopRow = 0; - } else if (_hasPatternLoop) { - _hasPatternLoop = false; - _row = _patternLoopRow; - } - if (_row >= 64) { - _row = 0; - _pos = (_pos + 1) % _module.songlen; - _patternLoopRow = 0; - } - - updateRow(); - } else - updateEffects(); - - _tick = (_tick + 1) % (_speed + _patternDelay * _speed); - if (_tick == 0) { - _row++; - _patternDelay = 0; - } - - for (track = 0; track < 4; track++) { - setChannelVolume(track, _track[track].vol); - setChannelPeriod(track, _track[track].period + _track[track].vibrato); - if (_track[track].sample) { - sample_t &sample = _module.sample[_track[track].sample - 1]; - setChannelData(track, - sample.data, - sample.replen > 2 ? sample.data + sample.repeat : 0, - sample.len, - sample.replen); - setChannelOffset(track, _track[track].offset); - _track[track].sample = 0; - } - } -} - -} // End of namespace Modules - -namespace Audio { - -AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo) { - return new Modules::ProtrackerStream(stream, offs, rate, stereo); -} - -} // End of namespace Audio diff --git a/sound/mods/protracker.h b/sound/mods/protracker.h deleted file mode 100644 index af722637c7..0000000000 --- a/sound/mods/protracker.h +++ /dev/null @@ -1,57 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - agos - * - parallaction - */ - -#ifndef SOUND_MODS_PROTRACKER_H -#define SOUND_MODS_PROTRACKER_H - -#include "common/stream.h" - -namespace Audio { - -class AudioStream; - -/* - * Factory function for ProTracker streams. Reads all data from the - * given ReadStream and creates an AudioStream from this. No reference - * to the 'stream' object is kept, so you can safely delete it after - * invoking this factory. - * - * @param stream the ReadStream from which to read the ProTracker data - * @param rate TODO - * @param stereo TODO - * @return a new AudioStream, or NULL, if an error occurred - */ -AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs = 0, int rate = 44100, bool stereo = true); - -} // End of namespace Audio - -#endif diff --git a/sound/mods/rjp1.cpp b/sound/mods/rjp1.cpp deleted file mode 100644 index be376d61a4..0000000000 --- a/sound/mods/rjp1.cpp +++ /dev/null @@ -1,582 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "common/debug.h" -#include "common/endian.h" - -#include "sound/mods/paula.h" -#include "sound/mods/rjp1.h" -#include "sound/audiostream.h" - -namespace Audio { - -struct Rjp1Channel { - const int8 *waveData; - const int8 *modulatePeriodData; - const int8 *modulateVolumeData; - const int8 *envelopeData; - uint16 volumeScale; - int16 volume; - uint16 modulatePeriodBase; - uint32 modulatePeriodLimit; - uint32 modulatePeriodIndex; - uint16 modulateVolumeBase; - uint32 modulateVolumeLimit; - uint32 modulateVolumeIndex; - uint8 freqStep; - uint32 freqInc; - uint32 freqInit; - const uint8 *noteData; - const uint8 *sequenceOffsets; - const uint8 *sequenceData; - uint8 loopSeqCount; - uint8 loopSeqCur; - uint8 loopSeq2Count; - uint8 loopSeq2Cur; - bool active; - int16 modulatePeriodInit; - int16 modulatePeriodNext; - bool setupNewNote; - int8 envelopeMode; - int8 envelopeScale; - int8 envelopeEnd1; - int8 envelopeEnd2; - int8 envelopeStart; - int8 envelopeVolume; - uint8 currentInstrument; - const int8 *data; - uint16 pos; - uint16 len; - uint16 repeatPos; - uint16 repeatLen; - bool isSfx; -}; - -class Rjp1 : public Paula { -public: - - struct Vars { - int8 *instData; - uint8 *songData[7]; - uint8 activeChannelsMask; - uint8 currentChannel; - int subsongsCount; - int instrumentsCount; - }; - - Rjp1(int rate, bool stereo); - virtual ~Rjp1(); - - bool load(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData); - void unload(); - - void startPattern(int ch, int pat); - void startSong(int song); - -protected: - - void startSequence(uint8 channelNum, uint8 seqNum); - void turnOffChannel(Rjp1Channel *channel); - void playChannel(Rjp1Channel *channel); - void turnOnChannel(Rjp1Channel *channel); - bool executeSfxSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p); - bool executeSongSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p); - void playSongSequence(Rjp1Channel *channel); - void modulateVolume(Rjp1Channel *channel); - void modulatePeriod(Rjp1Channel *channel); - void setupNote(Rjp1Channel *channel, int16 freq); - void setupInstrument(Rjp1Channel *channel, uint8 num); - void setRelease(Rjp1Channel *channel); - void modulateVolumeEnvelope(Rjp1Channel *channel); - void setSustain(Rjp1Channel *channel); - void setDecay(Rjp1Channel *channel); - void modulateVolumeWaveform(Rjp1Channel *channel); - void setVolume(Rjp1Channel *channel); - - void stopPaulaChannel(uint8 channel); - void setupPaulaChannel(uint8 channel, const int8 *waveData, uint16 offset, uint16 len, uint16 repeatPos, uint16 repeatLen); - - virtual void interrupt(); - - Vars _vars; - Rjp1Channel _channelsTable[4]; - - static const int16 _periodsTable[]; - static const int _periodsCount; -}; - -Rjp1::Rjp1(int rate, bool stereo) - : Paula(stereo, rate, rate / 50) { - memset(&_vars, 0, sizeof(_vars)); - memset(_channelsTable, 0, sizeof(_channelsTable)); -} - -Rjp1::~Rjp1() { - unload(); -} - -bool Rjp1::load(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData) { - if (songData->readUint32BE() == MKID_BE('RJP1') && songData->readUint32BE() == MKID_BE('SMOD')) { - for (int i = 0; i < 7; ++i) { - uint32 size = songData->readUint32BE(); - _vars.songData[i] = (uint8 *)malloc(size); - if (!_vars.songData[i]) - return false; - - songData->read(_vars.songData[i], size); - switch (i) { - case 0: - _vars.instrumentsCount = size / 32; - break; - case 1: - break; - case 2: - // sequence index to offsets, 1 per channel - _vars.subsongsCount = size / 4; - break; - case 3: - case 4: - // sequence offsets - break; - case 5: - case 6: - // sequence data - break; - } - } - - if (instrumentsData->readUint32BE() == MKID_BE('RJP1')) { - uint32 size = instrumentsData->size() - 4; - _vars.instData = (int8 *)malloc(size); - if (!_vars.instData) - return false; - - instrumentsData->read(_vars.instData, size); - - } - } - - debug(5, "Rjp1::load() _instrumentsCount = %d _subsongsCount = %d", _vars.instrumentsCount, _vars.subsongsCount); - return true; -} - -void Rjp1::unload() { - for (int i = 0; i < 7; ++i) { - free(_vars.songData[i]); - } - free(_vars.instData); - memset(&_vars, 0, sizeof(_vars)); - memset(_channelsTable, 0, sizeof(_channelsTable)); -} - -void Rjp1::startPattern(int ch, int pat) { - Rjp1Channel *channel = &_channelsTable[ch]; - _vars.activeChannelsMask |= 1 << ch; - channel->sequenceData = READ_BE_UINT32(_vars.songData[4] + pat * 4) + _vars.songData[6]; - channel->loopSeqCount = 6; - channel->loopSeqCur = channel->loopSeq2Cur = 1; - channel->active = true; - channel->isSfx = true; - // "start" Paula audiostream - startPaula(); -} - -void Rjp1::startSong(int song) { - if (song == 0 || song >= _vars.subsongsCount) { - warning("Invalid subsong number %d, defaulting to 1", song); - song = 1; - } - const uint8 *p = _vars.songData[2] + (song & 0x3F) * 4; - for (int i = 0; i < 4; ++i) { - uint8 seq = *p++; - if (seq) { - startSequence(i, seq); - } - } - // "start" Paula audiostream - startPaula(); -} - -void Rjp1::startSequence(uint8 channelNum, uint8 seqNum) { - Rjp1Channel *channel = &_channelsTable[channelNum]; - _vars.activeChannelsMask |= 1 << channelNum; - if (seqNum != 0) { - const uint8 *p = READ_BE_UINT32(_vars.songData[3] + seqNum * 4) + _vars.songData[5]; - uint8 seq = *p++; - channel->sequenceOffsets = p; - channel->sequenceData = READ_BE_UINT32(_vars.songData[4] + seq * 4) + _vars.songData[6]; - channel->loopSeqCount = 6; - channel->loopSeqCur = channel->loopSeq2Cur = 1; - channel->active = true; - } else { - channel->active = false; - turnOffChannel(channel); - } -} - -void Rjp1::turnOffChannel(Rjp1Channel *channel) { - stopPaulaChannel(channel - _channelsTable); -} - -void Rjp1::playChannel(Rjp1Channel *channel) { - if (channel->active) { - turnOnChannel(channel); - if (channel->sequenceData) { - playSongSequence(channel); - } - modulateVolume(channel); - modulatePeriod(channel); - } -} - -void Rjp1::turnOnChannel(Rjp1Channel *channel) { - if (channel->setupNewNote) { - channel->setupNewNote = false; - setupPaulaChannel(channel - _channelsTable, channel->data, channel->pos, channel->len, channel->repeatPos, channel->repeatLen); - } -} - -bool Rjp1::executeSfxSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p) { - bool loop = true; - switch (code & 7) { - case 0: - _vars.activeChannelsMask &= ~(1 << _vars.currentChannel); - loop = false; - stopPaula(); - break; - case 1: - setRelease(channel); - loop = false; - break; - case 2: - channel->loopSeqCount = *p++; - break; - case 3: - channel->loopSeq2Count = *p++; - break; - case 4: - code = *p++; - if (code != 0) { - setupInstrument(channel, code); - } - break; - case 7: - loop = false; - break; - } - return loop; -} - -bool Rjp1::executeSongSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p) { - bool loop = true; - const uint8 *offs; - switch (code & 7) { - case 0: - offs = channel->sequenceOffsets; - channel->loopSeq2Count = 1; - while (1) { - code = *offs++; - if (code != 0) { - channel->sequenceOffsets = offs; - p = READ_BE_UINT32(_vars.songData[4] + code * 4) + _vars.songData[6]; - break; - } else { - code = offs[0]; - if (code == 0) { - p = 0; - channel->active = false; - _vars.activeChannelsMask &= ~(1 << _vars.currentChannel); - loop = false; - break; - } else if (code & 0x80) { - code = offs[1]; - offs = READ_BE_UINT32(_vars.songData[3] + code * 4) + _vars.songData[5]; - } else { - offs -= code; - } - } - } - break; - case 1: - setRelease(channel); - loop = false; - break; - case 2: - channel->loopSeqCount = *p++; - break; - case 3: - channel->loopSeq2Count = *p++; - break; - case 4: - code = *p++; - if (code != 0) { - setupInstrument(channel, code); - } - break; - case 5: - channel->volumeScale = *p++; - break; - case 6: - channel->freqStep = *p++; - channel->freqInc = READ_BE_UINT32(p); p += 4; - channel->freqInit = 0; - break; - case 7: - loop = false; - break; - } - return loop; -} - -void Rjp1::playSongSequence(Rjp1Channel *channel) { - const uint8 *p = channel->sequenceData; - --channel->loopSeqCur; - if (channel->loopSeqCur == 0) { - --channel->loopSeq2Cur; - if (channel->loopSeq2Cur == 0) { - bool loop = true; - do { - uint8 code = *p++; - if (code & 0x80) { - if (channel->isSfx) { - loop = executeSfxSequenceOp(channel, code, p); - } else { - loop = executeSongSequenceOp(channel, code, p); - } - } else { - code >>= 1; - if (code < _periodsCount) { - setupNote(channel, _periodsTable[code]); - } - loop = false; - } - } while (loop); - channel->sequenceData = p; - channel->loopSeq2Cur = channel->loopSeq2Count; - } - channel->loopSeqCur = channel->loopSeqCount; - } -} - -void Rjp1::modulateVolume(Rjp1Channel *channel) { - modulateVolumeEnvelope(channel); - modulateVolumeWaveform(channel); - setVolume(channel); -} - -void Rjp1::modulatePeriod(Rjp1Channel *channel) { - if (channel->modulatePeriodData) { - uint32 per = channel->modulatePeriodIndex; - int period = (channel->modulatePeriodData[per] * channel->modulatePeriodInit) / 128; - period = -period; - if (period < 0) { - period /= 2; - } - channel->modulatePeriodNext = period + channel->modulatePeriodInit; - ++per; - if (per == channel->modulatePeriodLimit) { - per = channel->modulatePeriodBase * 2; - } - channel->modulatePeriodIndex = per; - } - if (channel->freqStep != 0) { - channel->freqInit += channel->freqInc; - --channel->freqStep; - } - setChannelPeriod(channel - _channelsTable, channel->freqInit + channel->modulatePeriodNext); -} - -void Rjp1::setupNote(Rjp1Channel *channel, int16 period) { - const uint8 *note = channel->noteData; - if (note) { - channel->modulatePeriodInit = channel->modulatePeriodNext = period; - channel->freqInit = 0; - const int8 *e = (const int8 *)_vars.songData[1] + READ_BE_UINT16(note + 12); - channel->envelopeData = e; - channel->envelopeStart = e[1]; - channel->envelopeScale = e[1] - e[0]; - channel->envelopeEnd2 = e[2]; - channel->envelopeEnd1 = e[2]; - channel->envelopeMode = 4; - channel->data = channel->waveData; - channel->pos = READ_BE_UINT16(note + 16); - channel->len = channel->pos + READ_BE_UINT16(note + 18); - channel->setupNewNote = true; - } -} - -void Rjp1::setupInstrument(Rjp1Channel *channel, uint8 num) { - if (channel->currentInstrument != num) { - channel->currentInstrument = num; - const uint8 *p = _vars.songData[0] + num * 32; - channel->noteData = p; - channel->repeatPos = READ_BE_UINT16(p + 20); - channel->repeatLen = READ_BE_UINT16(p + 22); - channel->volumeScale = READ_BE_UINT16(p + 14); - channel->modulatePeriodBase = READ_BE_UINT16(p + 24); - channel->modulatePeriodIndex = 0; - channel->modulatePeriodLimit = READ_BE_UINT16(p + 26) * 2; - channel->modulateVolumeBase = READ_BE_UINT16(p + 28); - channel->modulateVolumeIndex = 0; - channel->modulateVolumeLimit = READ_BE_UINT16(p + 30) * 2; - channel->waveData = _vars.instData + READ_BE_UINT32(p); - uint32 off = READ_BE_UINT32(p + 4); - if (off) { - channel->modulatePeriodData = _vars.instData + off; - } - off = READ_BE_UINT32(p + 8); - if (off) { - channel->modulateVolumeData = _vars.instData + off; - } - } -} - -void Rjp1::setRelease(Rjp1Channel *channel) { - const int8 *e = channel->envelopeData; - if (e) { - channel->envelopeStart = 0; - channel->envelopeScale = -channel->envelopeVolume; - channel->envelopeEnd2 = e[5]; - channel->envelopeEnd1 = e[5]; - channel->envelopeMode = -1; - } -} - -void Rjp1::modulateVolumeEnvelope(Rjp1Channel *channel) { - if (channel->envelopeMode) { - int16 es = channel->envelopeScale; - if (es) { - int8 m = channel->envelopeEnd1; - if (m == 0) { - es = 0; - } else { - es *= m; - m = channel->envelopeEnd2; - if (m == 0) { - es = 0; - } else { - es /= m; - } - } - } - channel->envelopeVolume = channel->envelopeStart - es; - --channel->envelopeEnd1; - if (channel->envelopeEnd1 == -1) { - switch (channel->envelopeMode) { - case 0: - break; - case 2: - setSustain(channel); - break; - case 4: - setDecay(channel); - break; - case -1: - setSustain(channel); - break; - default: - error("Unhandled envelope mode %d", channel->envelopeMode); - break; - } - return; - } - } - channel->volume = channel->envelopeVolume; -} - -void Rjp1::setSustain(Rjp1Channel *channel) { - channel->envelopeMode = 0; -} - -void Rjp1::setDecay(Rjp1Channel *channel) { - const int8 *e = channel->envelopeData; - if (e) { - channel->envelopeStart = e[3]; - channel->envelopeScale = e[3] - e[1]; - channel->envelopeEnd2 = e[4]; - channel->envelopeEnd1 = e[4]; - channel->envelopeMode = 2; - } -} - -void Rjp1::modulateVolumeWaveform(Rjp1Channel *channel) { - if (channel->modulateVolumeData) { - uint32 i = channel->modulateVolumeIndex; - channel->volume += channel->modulateVolumeData[i] * channel->volume / 128; - ++i; - if (i == channel->modulateVolumeLimit) { - i = channel->modulateVolumeBase * 2; - } - channel->modulateVolumeIndex = i; - } -} - -void Rjp1::setVolume(Rjp1Channel *channel) { - channel->volume = (channel->volume * channel->volumeScale) / 64; - channel->volume = CLIP(channel->volume, 0, 64); - setChannelVolume(channel - _channelsTable, channel->volume); -} - -void Rjp1::stopPaulaChannel(uint8 channel) { - clearVoice(channel); -} - -void Rjp1::setupPaulaChannel(uint8 channel, const int8 *waveData, uint16 offset, uint16 len, uint16 repeatPos, uint16 repeatLen) { - if (waveData) { - setChannelData(channel, waveData, waveData + repeatPos * 2, len * 2, repeatLen * 2, offset * 2); - } -} - -void Rjp1::interrupt() { - for (int i = 0; i < 4; ++i) { - _vars.currentChannel = i; - playChannel(&_channelsTable[i]); - } -} - -const int16 Rjp1::_periodsTable[] = { - 0x01C5, 0x01E0, 0x01FC, 0x021A, 0x023A, 0x025C, 0x0280, 0x02A6, 0x02D0, - 0x02FA, 0x0328, 0x0358, 0x00E2, 0x00F0, 0x00FE, 0x010D, 0x011D, 0x012E, - 0x0140, 0x0153, 0x0168, 0x017D, 0x0194, 0x01AC, 0x0071, 0x0078, 0x007F, - 0x0087, 0x008F, 0x0097, 0x00A0, 0x00AA, 0x00B4, 0x00BE, 0x00CA, 0x00D6 -}; - -const int Rjp1::_periodsCount = ARRAYSIZE(_periodsTable); - -AudioStream *makeRjp1Stream(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData, int num, int rate, bool stereo) { - Rjp1 *stream = new Rjp1(rate, stereo); - if (stream->load(songData, instrumentsData)) { - if (num < 0) { - stream->startPattern(3, -num); - } else { - stream->startSong(num); - } - return stream; - } - delete stream; - return 0; -} - -} // End of namespace Audio diff --git a/sound/mods/rjp1.h b/sound/mods/rjp1.h deleted file mode 100644 index e1960921b2..0000000000 --- a/sound/mods/rjp1.h +++ /dev/null @@ -1,50 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - queen - */ - -#ifndef SOUND_MODS_RJP1_H -#define SOUND_MODS_RJP1_H - -#include "common/stream.h" - -namespace Audio { - -class AudioStream; - -/* - * Factory function for RichardJoseph1 modules. Reads all data from the - * given songData and instrumentsData streams and creates an AudioStream - * from this. No references to these stream objects are kept. - */ -AudioStream *makeRjp1Stream(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData, int num, int rate = 44100, bool stereo = true); - -} // End of namespace Audio - -#endif diff --git a/sound/mods/soundfx.cpp b/sound/mods/soundfx.cpp deleted file mode 100644 index 3af8ca19c6..0000000000 --- a/sound/mods/soundfx.cpp +++ /dev/null @@ -1,275 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "common/endian.h" - -#include "sound/mods/paula.h" -#include "sound/mods/soundfx.h" -#include "sound/audiostream.h" - -namespace Audio { - -struct SoundFxInstrument { - char name[23]; - uint16 len; - uint8 finetune; - uint8 volume; - uint16 repeatPos; - uint16 repeatLen; - int8 *data; -}; - -class SoundFx : public Paula { -public: - - enum { - NUM_CHANNELS = 4, - NUM_INSTRUMENTS = 15 - }; - - SoundFx(int rate, bool stereo); - virtual ~SoundFx(); - - bool load(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb); - void play(); - -protected: - - void handlePattern(int ch, uint32 pat); - void updateEffects(int ch); - void handleTick(); - - void disablePaulaChannel(uint8 channel); - void setupPaulaChannel(uint8 channel, const int8 *data, uint16 len, uint16 repeatPos, uint16 repeatLen); - - virtual void interrupt(); - - uint8 _ticks; - uint16 _delay; - SoundFxInstrument _instruments[NUM_INSTRUMENTS]; - uint8 _numOrders; - uint8 _curOrder; - uint16 _curPos; - uint8 _ordersTable[128]; - uint8 *_patternData; - uint16 _effects[NUM_CHANNELS]; -}; - -SoundFx::SoundFx(int rate, bool stereo) - : Paula(stereo, rate) { - setTimerBaseValue(kPalCiaClock); - _ticks = 0; - _delay = 0; - memset(_instruments, 0, sizeof(_instruments)); - _numOrders = 0; - _curOrder = 0; - _curPos = 0; - memset(_ordersTable, 0, sizeof(_ordersTable)); - _patternData = 0; - memset(_effects, 0, sizeof(_effects)); -} - -SoundFx::~SoundFx() { - free(_patternData); - for (int i = 0; i < NUM_INSTRUMENTS; ++i) { - free(_instruments[i].data); - } -} - -bool SoundFx::load(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb) { - int instrumentsSize[15]; - if (!loadCb) { - for (int i = 0; i < NUM_INSTRUMENTS; ++i) { - instrumentsSize[i] = data->readUint32BE(); - } - } - uint8 tag[4]; - data->read(tag, 4); - if (memcmp(tag, "SONG", 4) != 0) { - return false; - } - _delay = data->readUint16BE(); - data->skip(7 * 2); - for (int i = 0; i < NUM_INSTRUMENTS; ++i) { - SoundFxInstrument *ins = &_instruments[i]; - data->read(ins->name, 22); ins->name[22] = 0; - ins->len = data->readUint16BE(); - ins->finetune = data->readByte(); - ins->volume = data->readByte(); - ins->repeatPos = data->readUint16BE(); - ins->repeatLen = data->readUint16BE(); - } - _numOrders = data->readByte(); - data->skip(1); - data->read(_ordersTable, 128); - int maxOrder = 0; - for (int i = 0; i < _numOrders; ++i) { - if (_ordersTable[i] > maxOrder) { - maxOrder = _ordersTable[i]; - } - } - int patternSize = (maxOrder + 1) * 4 * 4 * 64; - _patternData = (uint8 *)malloc(patternSize); - if (!_patternData) { - return false; - } - data->read(_patternData, patternSize); - for (int i = 0; i < NUM_INSTRUMENTS; ++i) { - SoundFxInstrument *ins = &_instruments[i]; - if (!loadCb) { - if (instrumentsSize[i] != 0) { - assert(ins->len <= 1 || ins->len * 2 <= instrumentsSize[i]); - assert(ins->repeatLen <= 1 || (ins->repeatPos + ins->repeatLen) * 2 <= instrumentsSize[i]); - ins->data = (int8 *)malloc(instrumentsSize[i]); - if (!ins->data) { - return false; - } - data->read(ins->data, instrumentsSize[i]); - } - } else { - if (ins->name[0]) { - ins->name[8] = '\0'; - ins->data = (int8 *)(*loadCb)(ins->name, 0); - if (!ins->data) { - return false; - } - } - } - } - return true; -} - -void SoundFx::play() { - _curPos = 0; - _curOrder = 0; - _ticks = 0; - setInterruptFreqUnscaled(_delay); - startPaula(); -} - -void SoundFx::handlePattern(int ch, uint32 pat) { - uint16 note1 = pat >> 16; - uint16 note2 = pat & 0xFFFF; - if (note1 == 0xFFFD) { // PIC - _effects[ch] = 0; - return; - } - _effects[ch] = note2; - if (note1 == 0xFFFE) { // STP - disablePaulaChannel(ch); - return; - } - int ins = (note2 & 0xF000) >> 12; - if (ins != 0) { - SoundFxInstrument *i = &_instruments[ins - 1]; - setupPaulaChannel(ch, i->data, i->len, i->repeatPos, i->repeatLen); - int effect = (note2 & 0xF00) >> 8; - int volume = i->volume; - switch (effect) { - case 5: // volume up - volume += (note2 & 0xFF); - if (volume > 63) { - volume = 63; - } - break; - case 6: // volume down - volume -= (note2 & 0xFF); - if (volume < 0) { - volume = 0; - } - break; - } - setChannelVolume(ch, volume); - } - if (note1 != 0) { - setChannelPeriod(ch, note1); - } -} - -void SoundFx::updateEffects(int ch) { - // updateEffects() is a no-op in all Delphine Software games using SoundFx : FW,OS,Cruise,AW - if (_effects[ch] != 0) { - switch (_effects[ch]) { - case 1: // appreggiato - case 2: // pitchbend - case 3: // ledon, enable low-pass filter - case 4: // ledoff, disable low-pass filter - case 7: // set step up - case 8: // set step down - warning("Unhandled effect %d", _effects[ch]); - break; - } - } -} - -void SoundFx::handleTick() { - ++_ticks; - if (_ticks != 6) { - for (int ch = 0; ch < 4; ++ch) { - updateEffects(ch); - } - } else { - _ticks = 0; - const uint8 *patternData = _patternData + _ordersTable[_curOrder] * 1024 + _curPos; - for (int ch = 0; ch < 4; ++ch) { - handlePattern(ch, READ_BE_UINT32(patternData)); - patternData += 4; - } - _curPos += 4 * 4; - if (_curPos >= 1024) { - _curPos = 0; - ++_curOrder; - if (_curOrder == _numOrders) { - stopPaula(); - } - } - } -} - -void SoundFx::disablePaulaChannel(uint8 channel) { - disableChannel(channel); -} - -void SoundFx::setupPaulaChannel(uint8 channel, const int8 *data, uint16 len, uint16 repeatPos, uint16 repeatLen) { - if (data && len > 1) { - setChannelData(channel, data, data + repeatPos * 2, len * 2, repeatLen * 2); - } -} - -void SoundFx::interrupt() { - handleTick(); -} - -AudioStream *makeSoundFxStream(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb, int rate, bool stereo) { - SoundFx *stream = new SoundFx(rate, stereo); - if (stream->load(data, loadCb)) { - stream->play(); - return stream; - } - delete stream; - return 0; -} - -} // End of namespace Audio diff --git a/sound/mods/soundfx.h b/sound/mods/soundfx.h deleted file mode 100644 index 089c19d292..0000000000 --- a/sound/mods/soundfx.h +++ /dev/null @@ -1,53 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/** - * @file - * Sound decoder used in engines: - * - cine - */ - -#ifndef SOUND_MODS_SOUNDFX_H -#define SOUND_MODS_SOUNDFX_H - -#include "common/stream.h" - -namespace Audio { - -class AudioStream; - -typedef byte *(*LoadSoundFxInstrumentCallback)(const char *name, uint32 *size); - -/* - * Factory function for SoundFX modules. Reads all data from the - * given data stream and creates an AudioStream from this (no references to the - * stream object is kept). If loadCb is non 0, then instruments are loaded using - * it, buffers returned are free'd at the end of playback. - */ -AudioStream *makeSoundFxStream(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb, int rate = 44100, bool stereo = true); - -} // End of namespace Audio - -#endif diff --git a/sound/mods/tfmx.cpp b/sound/mods/tfmx.cpp deleted file mode 100644 index 6ed1abcfb5..0000000000 --- a/sound/mods/tfmx.cpp +++ /dev/null @@ -1,1193 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "common/scummsys.h" -#include "common/endian.h" -#include "common/stream.h" -#include "common/util.h" -#include "common/debug.h" - -#include "sound/mods/tfmx.h" - -// test for engines using this class. -#if defined(SOUND_MODS_TFMX_H) - -// couple debug-functions -namespace { - -#if 0 -void displayPatternstep(const void * const vptr); -void displayMacroStep(const void * const vptr); -#endif - -static const uint16 noteIntervalls[64] = { - 1710, 1614, 1524, 1438, 1357, 1281, 1209, 1141, 1077, 1017, 960, 908, - 856, 810, 764, 720, 680, 642, 606, 571, 539, 509, 480, 454, - 428, 404, 381, 360, 340, 320, 303, 286, 270, 254, 240, 227, - 214, 202, 191, 180, 170, 160, 151, 143, 135, 127, 120, 113, - 214, 202, 191, 180, 170, 160, 151, 143, 135, 127, 120, 113, - 214, 202, 191, 180 -}; - -} // End of anonymous namespace - -namespace Audio { - -Tfmx::Tfmx(int rate, bool stereo) - : Paula(stereo, rate), - _resource(), - _resourceSample(), - _playerCtx(), - _deleteResource(false) { - - _playerCtx.stopWithLastPattern = false; - - for (int i = 0; i < kNumVoices; ++i) - _channelCtx[i].paulaChannel = (byte)i; - - _playerCtx.volume = 0x40; - _playerCtx.patternSkip = 6; - stopSongImpl(); - - setTimerBaseValue(kPalCiaClock); - setInterruptFreqUnscaled(kPalDefaultCiaVal); -} - -Tfmx::~Tfmx() { - freeResourceDataImpl(); -} - -void Tfmx::interrupt() { - assert(!_end); - ++_playerCtx.tickCount; - - for (int i = 0; i < kNumVoices; ++i) { - if (_channelCtx[i].dmaIntCount) { - // wait for DMA Interupts to happen - int doneDma = getChannelDmaCount(i); - if (doneDma >= _channelCtx[i].dmaIntCount) { - _channelCtx[i].dmaIntCount = 0; - _channelCtx[i].macroRun = true; - } - } - } - - for (int i = 0; i < kNumVoices; ++i) { - ChannelContext &channel = _channelCtx[i]; - - if (channel.sfxLockTime >= 0) - --channel.sfxLockTime; - else { - channel.sfxLocked = false; - channel.customMacroPrio = 0; - } - - // externally queued macros - if (channel.customMacro) { - const byte * const noteCmd = (const byte *)&channel.customMacro; - channel.sfxLocked = false; - noteCommand(noteCmd[0], noteCmd[1], (noteCmd[2] & 0xF0) | (uint8)i, noteCmd[3]); - channel.customMacro = 0; - channel.sfxLocked = (channel.customMacroPrio != 0); - } - - // apply timebased effects on Parameters - if (channel.macroSfxRun > 0) - effects(channel); - - // see if we have to run the macro-program - if (channel.macroRun) { - if (!channel.macroWait) - macroRun(channel); - else - --channel.macroWait; - } - - Paula::setChannelPeriod(i, channel.period); - if (channel.macroSfxRun >= 0) - channel.macroSfxRun = 1; - - // TODO: handling pending DMAOff? - } - - // Patterns are only processed each _playerCtx.timerCount + 1 tick - if (_playerCtx.song >= 0 && !_playerCtx.patternCount--) { - _playerCtx.patternCount = _playerCtx.patternSkip; - advancePatterns(); - } -} - -void Tfmx::effects(ChannelContext &channel) { - // addBegin - if (channel.addBeginLength) { - channel.sampleStart += channel.addBeginDelta; - Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart)); - if (!(--channel.addBeginCount)) { - channel.addBeginCount = channel.addBeginLength; - channel.addBeginDelta = -channel.addBeginDelta; - } - } - - // vibrato - if (channel.vibLength) { - channel.vibValue += channel.vibDelta; - if (--channel.vibCount == 0) { - channel.vibCount = channel.vibLength; - channel.vibDelta = -channel.vibDelta; - } - if (!channel.portaDelta) { - // 16x16 bit multiplication, casts needed for the right results - channel.period = (uint16)(((uint32)channel.refPeriod * (uint16)((1 << 11) + channel.vibValue)) >> 11); - } - } - - // portamento - if (channel.portaDelta && !(--channel.portaCount)) { - channel.portaCount = channel.portaSkip; - - bool resetPorta = true; - const uint16 period = channel.refPeriod; - uint16 portaVal = channel.portaValue; - - if (period > portaVal) { - portaVal = ((uint32)portaVal * (uint16)((1 << 8) + channel.portaDelta)) >> 8; - resetPorta = (period <= portaVal); - - } else if (period < portaVal) { - portaVal = ((uint32)portaVal * (uint16)((1 << 8) - channel.portaDelta)) >> 8; - resetPorta = (period >= portaVal); - } - - if (resetPorta) { - channel.portaDelta = 0; - channel.portaValue = period & 0x7FF; - } else - channel.period = channel.portaValue = portaVal & 0x7FF; - } - - // envelope - if (channel.envSkip && !channel.envCount--) { - channel.envCount = channel.envSkip; - - const int8 endVol = channel.envEndVolume; - int8 volume = channel.volume; - bool resetEnv = true; - - if (endVol > volume) { - volume += channel.envDelta; - resetEnv = endVol <= volume; - } else { - volume -= channel.envDelta; - resetEnv = volume <= 0 || endVol >= volume; - } - - if (resetEnv) { - channel.envSkip = 0; - volume = endVol; - } - channel.volume = volume; - } - - // Fade - if (_playerCtx.fadeDelta && !(--_playerCtx.fadeCount)) { - _playerCtx.fadeCount = _playerCtx.fadeSkip; - - _playerCtx.volume += _playerCtx.fadeDelta; - if (_playerCtx.volume == _playerCtx.fadeEndVolume) - _playerCtx.fadeDelta = 0; - } - - // Volume - const uint8 finVol = _playerCtx.volume * channel.volume >> 6; - Paula::setChannelVolume(channel.paulaChannel, finVol); -} - -void Tfmx::macroRun(ChannelContext &channel) { - bool deferWait = channel.deferWait; - for (;;) { - const byte *const macroPtr = (const byte *)(getMacroPtr(channel.macroOffset) + channel.macroStep); - ++channel.macroStep; - - switch (macroPtr[0]) { - case 0x00: // Reset + DMA Off. Parameters: deferWait, addset, vol - clearEffects(channel); - // FT - case 0x13: // DMA Off. Parameters: deferWait, addset, vol - // TODO: implement PArameters - Paula::disableChannel(channel.paulaChannel); - channel.deferWait = deferWait = (macroPtr[1] != 0); - if (deferWait) { - // if set, then we expect a DMA On in the same tick. - channel.period = 4; - //Paula::setChannelPeriod(channel.paulaChannel, channel.period); - Paula::setChannelSampleLen(channel.paulaChannel, 1); - // in this state we then need to allow some commands that normally - // would halt the macroprogamm to continue instead. - // those commands are: Wait, WaitDMA, AddPrevNote, AddNote, SetNote, - // DMA On is affected aswell - // TODO remember time disabled, remember pending dmaoff?. - } - - if (macroPtr[2] || macroPtr[3]) { - channel.volume = (macroPtr[2] ? 0 : channel.relVol * 3) + macroPtr[3]; - Paula::setChannelVolume(channel.paulaChannel, channel.volume); - } - continue; - - case 0x01: // DMA On - // TODO: Parameter macroPtr[1] - en-/disable effects - channel.dmaIntCount = 0; - if (deferWait) { - // TODO - // there is actually a small delay in the player, but I think that - // only allows to clear DMA-State on real Hardware - } - Paula::setChannelPeriod(channel.paulaChannel, channel.period); - Paula::enableChannel(channel.paulaChannel); - channel.deferWait = deferWait = false; - continue; - - case 0x02: // Set Beginn. Parameters: SampleOffset(L) - channel.addBeginLength = 0; - channel.sampleStart = READ_BE_UINT32(macroPtr) & 0xFFFFFF; - Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart)); - continue; - - case 0x03: // SetLength. Parameters: SampleLength(W) - channel.sampleLen = READ_BE_UINT16(¯oPtr[2]); - Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen); - continue; - - case 0x04: // Wait. Parameters: Ticks to wait(W). - // TODO: some unknown Parameter? (macroPtr[1] & 1) - channel.macroWait = READ_BE_UINT16(¯oPtr[2]); - break; - - case 0x10: // Loop Key Up. Parameters: Loopcount, MacroStep(W) - if (channel.keyUp) - continue; - // FT - case 0x05: // Loop. Parameters: Loopcount, MacroStep(W) - if (channel.macroLoopCount != 0) { - if (channel.macroLoopCount == 0xFF) - channel.macroLoopCount = macroPtr[1]; - channel.macroStep = READ_BE_UINT16(¯oPtr[2]); - } - --channel.macroLoopCount; - continue; - - case 0x06: // Jump. Parameters: MacroIndex, MacroStep(W) - // channel.macroIndex = macroPtr[1] & (kMaxMacroOffsets - 1); - channel.macroOffset = _resource->macroOffset[macroPtr[1] & (kMaxMacroOffsets - 1)]; - channel.macroStep = READ_BE_UINT16(¯oPtr[2]); - channel.macroLoopCount = 0xFF; - continue; - - case 0x07: // Stop Macro - channel.macroRun = false; - --channel.macroStep; - return; - - case 0x08: // AddNote. Parameters: Note, Finetune(W) - setNoteMacro(channel, channel.note + macroPtr[1], READ_BE_UINT16(¯oPtr[2])); - break; - - case 0x09: // SetNote. Parameters: Note, Finetune(W) - setNoteMacro(channel, macroPtr[1], READ_BE_UINT16(¯oPtr[2])); - break; - - case 0x0A: // Clear Effects - clearEffects(channel); - continue; - - case 0x0B: // Portamento. Parameters: count, speed - channel.portaSkip = macroPtr[1]; - channel.portaCount = 1; - // if porta is already running, then keep using old value - if (!channel.portaDelta) - channel.portaValue = channel.refPeriod; - channel.portaDelta = READ_BE_UINT16(¯oPtr[2]); - continue; - - case 0x0C: // Vibrato. Parameters: Speed, intensity - channel.vibLength = macroPtr[1]; - channel.vibCount = macroPtr[1] / 2; - channel.vibDelta = macroPtr[3]; - // TODO: Perhaps a bug, vibValue could be left uninitialised - if (!channel.portaDelta) { - channel.period = channel.refPeriod; - channel.vibValue = 0; - } - continue; - - case 0x0D: // Add Volume. Parameters: note, addNoteFlag, volume - if (macroPtr[2] == 0xFE) - setNoteMacro(channel, channel.note + macroPtr[1], 0); - channel.volume = channel.relVol * 3 + macroPtr[3]; - continue; - - case 0x0E: // Set Volume. Parameters: note, addNoteFlag, volume - if (macroPtr[2] == 0xFE) - setNoteMacro(channel, channel.note + macroPtr[1], 0); - channel.volume = macroPtr[3]; - continue; - - case 0x0F: // Envelope. Parameters: speed, count, endvol - channel.envDelta = macroPtr[1]; - channel.envCount = channel.envSkip = macroPtr[2]; - channel.envEndVolume = macroPtr[3]; - continue; - - case 0x11: // Add Beginn. Parameters: times, Offset(W) - channel.addBeginLength = channel.addBeginCount = macroPtr[1]; - channel.addBeginDelta = (int16)READ_BE_UINT16(¯oPtr[2]); - channel.sampleStart += channel.addBeginDelta; - Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart)); - continue; - - case 0x12: // Add Length. Parameters: added Length(W) - channel.sampleLen += (int16)READ_BE_UINT16(¯oPtr[2]); - Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen); - continue; - - case 0x14: // Wait key up. Parameters: wait cycles - if (channel.keyUp || channel.macroLoopCount == 0) { - channel.macroLoopCount = 0xFF; - continue; - } else if (channel.macroLoopCount == 0xFF) - channel.macroLoopCount = macroPtr[3]; - --channel.macroLoopCount; - --channel.macroStep; - return; - - case 0x15: // Subroutine. Parameters: MacroIndex, Macrostep(W) - channel.macroReturnOffset = channel.macroOffset; - channel.macroReturnStep = channel.macroStep; - - channel.macroOffset = _resource->macroOffset[macroPtr[1] & (kMaxMacroOffsets - 1)]; - channel.macroStep = READ_BE_UINT16(¯oPtr[2]); - // TODO: MI does some weird stuff there. Figure out which varioables need to be set - continue; - - case 0x16: // Return from Sub. - channel.macroOffset = channel.macroReturnOffset; - channel.macroStep = channel.macroReturnStep; - continue; - - case 0x17: // Set Period. Parameters: Period(W) - channel.refPeriod = READ_BE_UINT16(¯oPtr[2]); - if (!channel.portaDelta) { - channel.period = channel.refPeriod; - //Paula::setChannelPeriod(channel.paulaChannel, channel.period); - } - continue; - - case 0x18: { // Sampleloop. Parameters: Offset from Samplestart(W) - // TODO: MI loads 24 bit, but thats useless? - const uint16 temp = /* ((int8)macroPtr[1] << 16) | */ READ_BE_UINT16(¯oPtr[2]); - if (macroPtr[1] || (temp & 1)) - warning("Tfmx: Problematic value for sampleloop: %06X", (macroPtr[1] << 16) | temp); - channel.sampleStart += temp & 0xFFFE; - channel.sampleLen -= (temp / 2) /* & 0x7FFF */; - Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart)); - Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen); - continue; - } - case 0x19: // Set One-Shot Sample - channel.addBeginLength = 0; - channel.sampleStart = 0; - channel.sampleLen = 1; - Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(0)); - Paula::setChannelSampleLen(channel.paulaChannel, 1); - continue; - - case 0x1A: // Wait on DMA. Parameters: Cycles-1(W) to wait - channel.dmaIntCount = READ_BE_UINT16(¯oPtr[2]) + 1; - channel.macroRun = false; - Paula::setChannelDmaCount(channel.paulaChannel); - break; - -/* case 0x1B: // Random play. Parameters: macro/speed/mode - warnMacroUnimplemented(macroPtr, 0); - continue;*/ - - case 0x1C: // Branch on Note. Parameters: note/macrostep(W) - if (channel.note > macroPtr[1]) - channel.macroStep = READ_BE_UINT16(¯oPtr[2]); - continue; - - case 0x1D: // Branch on Volume. Parameters: volume/macrostep(W) - if (channel.volume > macroPtr[1]) - channel.macroStep = READ_BE_UINT16(¯oPtr[2]); - continue; - -/* case 0x1E: // Addvol+note. Parameters: note/CONST./volume - warnMacroUnimplemented(macroPtr, 0); - continue;*/ - - case 0x1F: // AddPrevNote. Parameters: Note, Finetune(W) - setNoteMacro(channel, channel.prevNote + macroPtr[1], READ_BE_UINT16(¯oPtr[2])); - break; - - case 0x20: // Signal. Parameters: signalnumber, value(W) - if (_playerCtx.numSignals > macroPtr[1]) - _playerCtx.signal[macroPtr[1]] = READ_BE_UINT16(¯oPtr[2]); - continue; - - case 0x21: // Play macro. Parameters: macro, chan, detune - noteCommand(channel.note, macroPtr[1], (channel.relVol << 4) | macroPtr[2], macroPtr[3]); - continue; - - // 0x22 - 0x29 are used by Gem`X - // 0x30 - 0x34 are used by Carribean Disaster - - default: - debug(3, "Tfmx: Macro %02X not supported", macroPtr[0]); - } - if (!deferWait) - return; - } -} - -void Tfmx::advancePatterns() { -startPatterns: - int runningPatterns = 0; - - for (int i = 0; i < kNumChannels; ++i) { - PatternContext &pattern = _patternCtx[i]; - const uint8 pattCmd = pattern.command; - if (pattCmd < 0x90) { // execute Patternstep - ++runningPatterns; - if (!pattern.wait) { - // issue all Steps for this tick - if (patternRun(pattern)) { - // we load the next Trackstep Command and then process all Channels again - if (trackRun(true)) - goto startPatterns; - else - break; - } - - } else - --pattern.wait; - - } else if (pattCmd == 0xFE) { // Stop voice in pattern.expose - pattern.command = 0xFF; - ChannelContext &channel = _channelCtx[pattern.expose & (kNumVoices - 1)]; - if (!channel.sfxLocked) { - haltMacroProgramm(channel); - Paula::disableChannel(channel.paulaChannel); - } - } // else this pattern-Channel is stopped - } - if (_playerCtx.stopWithLastPattern && !runningPatterns) { - stopPaula(); - } -} - -bool Tfmx::patternRun(PatternContext &pattern) { - for (;;) { - const byte *const patternPtr = (const byte *)(getPatternPtr(pattern.offset) + pattern.step); - ++pattern.step; - const byte pattCmd = patternPtr[0]; - - if (pattCmd < 0xF0) { // Playnote - bool doWait = false; - byte noteCmd = pattCmd + pattern.expose; - byte param3 = patternPtr[3]; - if (pattCmd < 0xC0) { // Note - if (pattCmd >= 0x80) { // Wait - pattern.wait = param3; - param3 = 0; - doWait = true; - } - noteCmd &= 0x3F; - } // else Portamento - noteCommand(noteCmd, patternPtr[1], patternPtr[2], param3); - if (doWait) - return false; - - } else { // Patterncommand - switch (pattCmd & 0xF) { - case 0: // End Pattern + Next Trackstep - pattern.command = 0xFF; - --pattern.step; - return true; - - case 1: // Loop Pattern. Parameters: Loopcount, PatternStep(W) - if (pattern.loopCount != 0) { - if (pattern.loopCount == 0xFF) - pattern.loopCount = patternPtr[1]; - pattern.step = READ_BE_UINT16(&patternPtr[2]); - } - --pattern.loopCount; - continue; - - case 2: // Jump. Parameters: PatternIndex, PatternStep(W) - pattern.offset = _resource->patternOffset[patternPtr[1] & (kMaxPatternOffsets - 1)]; - pattern.step = READ_BE_UINT16(&patternPtr[2]); - continue; - - case 3: // Wait. Paramters: ticks to wait - pattern.wait = patternPtr[1]; - return false; - - case 14: // Stop custompattern - // TODO apparently toggles on/off pattern channel 7 - debug(3, "Tfmx: Encountered 'Stop custompattern' command"); - // FT - case 4: // Stop this pattern - pattern.command = 0xFF; - --pattern.step; - // TODO: try figuring out if this was the last Channel? - return false; - - case 5: // Key Up Signal. Paramters: channel - if (!_channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLocked) - _channelCtx[patternPtr[2] & (kNumVoices - 1)].keyUp = true; - continue; - - case 6: // Vibrato. Parameters: length, channel, rate - case 7: // Envelope. Parameters: rate, tempo | channel, endVol - noteCommand(pattCmd, patternPtr[1], patternPtr[2], patternPtr[3]); - continue; - - case 8: // Subroutine. Parameters: pattern, patternstep(W) - pattern.savedOffset = pattern.offset; - pattern.savedStep = pattern.step; - - pattern.offset = _resource->patternOffset[patternPtr[1] & (kMaxPatternOffsets - 1)]; - pattern.step = READ_BE_UINT16(&patternPtr[2]); - continue; - - case 9: // Return from Subroutine - pattern.offset = pattern.savedOffset; - pattern.step = pattern.savedStep; - continue; - - case 10: // fade. Parameters: tempo, endVol - initFadeCommand((uint8)patternPtr[1], (int8)patternPtr[3]); - continue; - - case 11: // play pattern. Parameters: patternCmd, channel, expose - initPattern(_patternCtx[patternPtr[2] & (kNumChannels - 1)], patternPtr[1], patternPtr[3], _resource->patternOffset[patternPtr[1] & (kMaxPatternOffsets - 1)]); - continue; - - case 12: // Lock. Parameters: lockFlag, channel, lockTime - _channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLocked = (patternPtr[1] != 0); - _channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLockTime = patternPtr[3]; - continue; - - case 13: // Cue. Parameters: signalnumber, value(W) - if (_playerCtx.numSignals > patternPtr[1]) - _playerCtx.signal[patternPtr[1]] = READ_BE_UINT16(&patternPtr[2]); - continue; - - case 15: // NOP - continue; - } - } - } -} - -bool Tfmx::trackRun(const bool incStep) { - assert(_playerCtx.song >= 0); - if (incStep) { - // TODO Optionally disable looping - if (_trackCtx.posInd == _trackCtx.stopInd) - _trackCtx.posInd = _trackCtx.startInd; - else - ++_trackCtx.posInd; - } - for (;;) { - const uint16 *const trackData = getTrackPtr(_trackCtx.posInd); - - if (trackData[0] != FROM_BE_16(0xEFFE)) { - // 8 commands for Patterns - for (int i = 0; i < 8; ++i) { - const uint8 *patCmd = (const uint8 *)&trackData[i]; - // First byte is pattern number - const uint8 patNum = patCmd[0]; - // if highest bit is set then keep previous pattern - if (patNum < 0x80) { - initPattern(_patternCtx[i], patNum, patCmd[1], _resource->patternOffset[patNum]); - } else { - _patternCtx[i].command = patNum; - _patternCtx[i].expose = (int8)patCmd[1]; - } - } - return true; - - } else { - // 16 byte Trackstep Command - switch (READ_BE_UINT16(&trackData[1])) { - case 0: // Stop Player. No Parameters - stopPaula(); - return false; - - case 1: // Branch/Loop section of tracksteps. Parameters: branch target, loopcount - if (_trackCtx.loopCount != 0) { - if (_trackCtx.loopCount < 0) - _trackCtx.loopCount = READ_BE_UINT16(&trackData[3]); - _trackCtx.posInd = READ_BE_UINT16(&trackData[2]); - continue; - } - --_trackCtx.loopCount; - break; - - case 2: { // Set Tempo. Parameters: tempo, divisor - _playerCtx.patternCount = _playerCtx.patternSkip = READ_BE_UINT16(&trackData[2]); // tempo - const uint16 temp = READ_BE_UINT16(&trackData[3]); // divisor - - if (!(temp & 0x8000) && (temp & 0x1FF)) - setInterruptFreqUnscaled(temp & 0x1FF); - break; - } - case 4: // Fade. Parameters: tempo, endVol - // load the LSB of the 16bit words - initFadeCommand(((const uint8 *)&trackData[2])[1], ((const int8 *)&trackData[3])[1]); - break; - - case 3: // Unknown, stops player aswell - default: - debug(3, "Tfmx: Unknown Trackstep Command: %02X", READ_BE_UINT16(&trackData[1])); - // MI-Player handles this by stopping the player, we just continue - } - } - - if (_trackCtx.posInd == _trackCtx.stopInd) { - warning("Tfmx: Reached invalid Song-Position"); - return false; - } - ++_trackCtx.posInd; - } -} - -void Tfmx::noteCommand(const uint8 note, const uint8 param1, const uint8 param2, const uint8 param3) { - ChannelContext &channel = _channelCtx[param2 & (kNumVoices - 1)]; - - if (note == 0xFC) { // Lock command - channel.sfxLocked = (param1 != 0); - channel.sfxLockTime = param3; // only 1 byte read! - - } else if (channel.sfxLocked) { // Channel still locked, do nothing - - } else if (note < 0xC0) { // Play Note - Parameters: note, macro, relVol | channel, finetune - - channel.prevNote = channel.note; - channel.note = note; - // channel.macroIndex = param1 & (kMaxMacroOffsets - 1); - channel.macroOffset = _resource->macroOffset[param1 & (kMaxMacroOffsets - 1)]; - channel.relVol = param2 >> 4; - channel.fineTune = (int8)param3; - - // TODO: the point where the channel gets initialised varies with the games, needs more research. - initMacroProgramm(channel); - channel.keyUp = false; // key down = playing a Note - - } else if (note < 0xF0) { // Portamento - Parameters: note, tempo, channel, rate - channel.portaSkip = param1; - channel.portaCount = 1; - if (!channel.portaDelta) - channel.portaValue = channel.refPeriod; - channel.portaDelta = param3; - - channel.note = note & 0x3F; - channel.refPeriod = noteIntervalls[channel.note]; - - } else switch (note) { // Command - - case 0xF5: // Key Up Signal - channel.keyUp = true; - break; - - case 0xF6: // Vibratio - Parameters: length, channel, rate - channel.vibLength = param1 & 0xFE; - channel.vibCount = param1 / 2; - channel.vibDelta = param3; - channel.vibValue = 0; - break; - - case 0xF7: // Envelope - Parameters: rate, tempo | channel, endVol - channel.envDelta = param1; - channel.envCount = channel.envSkip = (param2 >> 4) + 1; - channel.envEndVolume = param3; - break; - } -} - -void Tfmx::initMacroProgramm(ChannelContext &channel) { - channel.macroStep = 0; - channel.macroWait = 0; - channel.macroRun = true; - channel.macroSfxRun = 0; - channel.macroLoopCount = 0xFF; - channel.dmaIntCount = 0; - channel.deferWait = false; - - channel.macroReturnOffset = 0; - channel.macroReturnStep = 0; -} - -void Tfmx::clearEffects(ChannelContext &channel) { - channel.addBeginLength = 0; - channel.envSkip = 0; - channel.vibLength = 0; - channel.portaDelta = 0; -} - -void Tfmx::haltMacroProgramm(ChannelContext &channel) { - channel.macroRun = false; - channel.dmaIntCount = 0; -} - -void Tfmx::unlockMacroChannel(ChannelContext &channel) { - channel.customMacro = 0; - channel.customMacroIndex = 0; - channel.customMacroPrio = 0; - channel.sfxLocked = false; - channel.sfxLockTime = -1; -} - -void Tfmx::initPattern(PatternContext &pattern, uint8 cmd, int8 expose, uint32 offset) { - pattern.command = cmd; - pattern.offset = offset; - pattern.expose = expose; - pattern.step = 0; - pattern.wait = 0; - pattern.loopCount = 0xFF; - - pattern.savedOffset = 0; - pattern.savedStep = 0; -} - -void Tfmx::stopSongImpl(bool stopAudio) { - _playerCtx.song = -1; - for (int i = 0; i < kNumChannels; ++i) { - _patternCtx[i].command = 0xFF; - _patternCtx[i].expose = 0; - } - if (stopAudio) { - stopPaula(); - for (int i = 0; i < kNumVoices; ++i) { - clearEffects(_channelCtx[i]); - unlockMacroChannel(_channelCtx[i]); - haltMacroProgramm(_channelCtx[i]); - _channelCtx[i].note = 0; - _channelCtx[i].volume = 0; - _channelCtx[i].macroSfxRun = -1; - _channelCtx[i].vibValue = 0; - - _channelCtx[i].sampleStart = 0; - _channelCtx[i].sampleLen = 2; - _channelCtx[i].refPeriod = 4; - _channelCtx[i].period = 4; - Paula::disableChannel(i); - } - } -} - -void Tfmx::setNoteMacro(ChannelContext &channel, uint note, int fineTune) { - const uint16 noteInt = noteIntervalls[note & 0x3F]; - const uint16 finetune = (uint16)(fineTune + channel.fineTune + (1 << 8)); - channel.refPeriod = ((uint32)noteInt * finetune >> 8); - if (!channel.portaDelta) - channel.period = channel.refPeriod; -} - -void Tfmx::initFadeCommand(const uint8 fadeTempo, const int8 endVol) { - _playerCtx.fadeCount = _playerCtx.fadeSkip = fadeTempo; - _playerCtx.fadeEndVolume = endVol; - - if (fadeTempo) { - const int diff = _playerCtx.fadeEndVolume - _playerCtx.volume; - _playerCtx.fadeDelta = (diff != 0) ? ((diff > 0) ? 1 : -1) : 0; - } else { - _playerCtx.volume = endVol; - _playerCtx.fadeDelta = 0; - } -} - -void Tfmx::setModuleData(Tfmx &otherPlayer) { - setModuleData(otherPlayer._resource, otherPlayer._resourceSample.sampleData, otherPlayer._resourceSample.sampleLen, false); -} - -bool Tfmx::load(Common::SeekableReadStream &musicData, Common::SeekableReadStream &sampleData, bool autoDelete) { - const MdatResource *mdat = loadMdatFile(musicData); - if (mdat) { - uint32 sampleLen = 0; - const int8 *sampleDat = loadSampleFile(sampleLen, sampleData); - if (sampleDat) { - setModuleData(mdat, sampleDat, sampleLen, autoDelete); - return true; - } - delete[] mdat->mdatAlloc; - delete mdat; - } - return false; -} - -void Tfmx::freeResourceDataImpl() { - if (_deleteResource) { - if (_resource) { - delete[] _resource->mdatAlloc; - delete _resource; - } - delete[] _resourceSample.sampleData; - } - _resource = 0; - _resourceSample.sampleData = 0; - _resourceSample.sampleLen = 0; - _deleteResource = false; -} - -void Tfmx::setModuleData(const MdatResource *resource, const int8 *sampleData, uint32 sampleLen, bool autoDelete) { - Common::StackLock lock(_mutex); - stopSongImpl(true); - freeResourceDataImpl(); - _resource = resource; - _resourceSample.sampleData = sampleData; - _resourceSample.sampleLen = sampleData ? sampleLen : 0; - _deleteResource = autoDelete; -} - -const int8 *Tfmx::loadSampleFile(uint32 &sampleLen, Common::SeekableReadStream &sampleStream) { - sampleLen = 0; - - const int32 sampleSize = sampleStream.size(); - if (sampleSize < 4) { - warning("Tfmx: Cant load Samplefile"); - return false; - } - - int8 *sampleAlloc = new int8[sampleSize]; - if (!sampleAlloc) { - warning("Tfmx: Could not allocate Memory: %dKB", sampleSize / 1024); - return 0; - } - - if (sampleStream.read(sampleAlloc, sampleSize) == (uint32)sampleSize) { - sampleAlloc[0] = sampleAlloc[1] = sampleAlloc[2] = sampleAlloc[3] = 0; - sampleLen = sampleSize; - } else { - delete[] sampleAlloc; - warning("Tfmx: Encountered IO-Error"); - return 0; - } - return sampleAlloc; -} - -const Tfmx::MdatResource *Tfmx::loadMdatFile(Common::SeekableReadStream &musicData) { - bool hasHeader = false; - const int32 mdatSize = musicData.size(); - if (mdatSize >= 0x200) { - byte buf[16] = { 0 }; - // 0x0000: 10 Bytes Header "TFMX-SONG " - musicData.read(buf, 10); - hasHeader = memcmp(buf, "TFMX-SONG ", 10) == 0; - } - - if (!hasHeader) { - warning("Tfmx: File is not a Tfmx Module"); - return 0; - } - - MdatResource *resource = new MdatResource; - - resource->mdatAlloc = 0; - resource->mdatData = 0; - resource->mdatLen = 0; - - // 0x000A: int16 flags - resource->headerFlags = musicData.readUint16BE(); - // 0x000C: int32 ? - // 0x0010: 6*40 Textfield - musicData.skip(4 + 6 * 40); - - /* 0x0100: Songstart x 32*/ - for (int i = 0; i < kNumSubsongs; ++i) - resource->subsong[i].songstart = musicData.readUint16BE(); - /* 0x0140: Songend x 32*/ - for (int i = 0; i < kNumSubsongs; ++i) - resource->subsong[i].songend = musicData.readUint16BE(); - /* 0x0180: Tempo x 32*/ - for (int i = 0; i < kNumSubsongs; ++i) - resource->subsong[i].tempo = musicData.readUint16BE(); - - /* 0x01c0: unused ? */ - musicData.skip(16); - - /* 0x01d0: trackstep, pattern data p, macro data p */ - const uint32 offTrackstep = musicData.readUint32BE(); - uint32 offPatternP, offMacroP; - - // This is how MI`s TFMX-Player tests for unpacked Modules. - if (offTrackstep == 0) { // unpacked File - resource->trackstepOffset = 0x600 + 0x200; - offPatternP = 0x200 + 0x200; - offMacroP = 0x400 + 0x200; - } else { // packed File - resource->trackstepOffset = offTrackstep; - offPatternP = musicData.readUint32BE(); - offMacroP = musicData.readUint32BE(); - } - - // End of basic header, check if everything worked ok - if (musicData.err()) { - warning("Tfmx: Encountered IO-Error"); - delete resource; - return 0; - } - - // TODO: if a File is packed it could have for Ex only 2 Patterns/Macros - // the following loops could then read beyond EOF. - // To correctly handle this it would be necessary to sort the pointers and - // figure out the number of Macros/Patterns - // We could also analyze pointers if they are correct offsets, - // so that accesses can be unchecked later - - // Read in pattern starting offsets - musicData.seek(offPatternP); - for (int i = 0; i < kMaxPatternOffsets; ++i) - resource->patternOffset[i] = musicData.readUint32BE(); - - // use last PatternOffset (stored at 0x5FC in mdat) if unpacked File - // or fixed offset 0x200 if packed - resource->sfxTableOffset = offTrackstep ? 0x200 : resource->patternOffset[127]; - - // Read in macro starting offsets - musicData.seek(offMacroP); - for (int i = 0; i < kMaxMacroOffsets; ++i) - resource->macroOffset[i] = musicData.readUint32BE(); - - // Read in mdat-file - // TODO: we can skip everything thats already stored in the resource-structure. - const int32 mdatOffset = offTrackstep ? 0x200 : 0x600; // 0x200 is very conservative - const uint32 allocSize = (uint32)mdatSize - mdatOffset; - - byte *mdatAlloc = new byte[allocSize]; - if (!mdatAlloc) { - warning("Tfmx: Could not allocate Memory: %dKB", allocSize / 1024); - delete resource; - return 0; - } - musicData.seek(mdatOffset); - if (musicData.read(mdatAlloc, allocSize) == allocSize) { - resource->mdatAlloc = mdatAlloc; - resource->mdatData = mdatAlloc - mdatOffset; - resource->mdatLen = mdatSize; - } else { - delete[] mdatAlloc; - warning("Tfmx: Encountered IO-Error"); - delete resource; - return 0; - } - - return resource; -} - -void Tfmx::doMacro(int note, int macro, int relVol, int finetune, int channelNo) { - assert(0 <= macro && macro < kMaxMacroOffsets); - assert(0 <= note && note < 0xC0); - Common::StackLock lock(_mutex); - - if (!hasResources()) - return; - channelNo &= (kNumVoices - 1); - ChannelContext &channel = _channelCtx[channelNo]; - unlockMacroChannel(channel); - - noteCommand((uint8)note, (uint8)macro, (uint8)((relVol << 4) | channelNo), (uint8)finetune); - startPaula(); -} - -void Tfmx::stopMacroEffect(int channel) { - assert(0 <= channel && channel < kNumVoices); - Common::StackLock lock(_mutex); - unlockMacroChannel(_channelCtx[channel]); - haltMacroProgramm(_channelCtx[channel]); - Paula::disableChannel(_channelCtx[channel].paulaChannel); -} - -void Tfmx::doSong(int songPos, bool stopAudio) { - assert(0 <= songPos && songPos < kNumSubsongs); - Common::StackLock lock(_mutex); - - stopSongImpl(stopAudio); - - if (!hasResources()) - return; - - _trackCtx.loopCount = -1; - _trackCtx.startInd = _trackCtx.posInd = _resource->subsong[songPos].songstart; - _trackCtx.stopInd = _resource->subsong[songPos].songend; - _playerCtx.song = (int8)songPos; - - const bool palFlag = (_resource->headerFlags & 2) != 0; - const uint16 tempo = _resource->subsong[songPos].tempo; - uint16 ciaIntervall; - if (tempo >= 0x10) { - ciaIntervall = (uint16)(kCiaBaseInterval / tempo); - _playerCtx.patternSkip = 0; - } else { - ciaIntervall = palFlag ? (uint16)kPalDefaultCiaVal : (uint16)kNtscDefaultCiaVal; - _playerCtx.patternSkip = tempo; - } - setInterruptFreqUnscaled(ciaIntervall); - Paula::setAudioFilter(true); - - _playerCtx.patternCount = 0; - if (trackRun()) - startPaula(); -} - -int Tfmx::doSfx(uint16 sfxIndex, bool unlockChannel) { - assert(sfxIndex < 128); - Common::StackLock lock(_mutex); - - if (!hasResources()) - return -1; - const byte *sfxEntry = getSfxPtr(sfxIndex); - if (sfxEntry[0] == 0xFB) { - warning("Tfmx: custom patterns are not supported"); - // custompattern - /* const uint8 patCmd = sfxEntry[2]; - const int8 patExp = (int8)sfxEntry[3]; */ - } else { - // custommacro - const byte channelNo = ((_playerCtx.song >= 0) ? sfxEntry[2] : sfxEntry[4]) & (kNumVoices - 1); - const byte priority = sfxEntry[5] & 0x7F; - - ChannelContext &channel = _channelCtx[channelNo]; - if (unlockChannel) - unlockMacroChannel(channel); - - const int16 sfxLocktime = channel.sfxLockTime; - if (priority >= channel.customMacroPrio || sfxLocktime < 0) { - if (sfxIndex != channel.customMacroIndex || sfxLocktime < 0 || (sfxEntry[5] < 0x80)) { - channel.customMacro = READ_UINT32(sfxEntry); // intentionally not "endian-correct" - channel.customMacroPrio = priority; - channel.customMacroIndex = (uint8)sfxIndex; - debug(3, "Tfmx: running Macro %08X on channel %i - priority: %02X", TO_BE_32(channel.customMacro), channelNo, priority); - return channelNo; - } - } - } - return -1; -} - -} // End of namespace Audio - -// some debugging functions -#if 0 -namespace { - -void displayMacroStep(const void * const vptr) { - static const char *tableMacros[] = { - "DMAoff+Resetxx/xx/xx flag/addset/vol ", - "DMAon (start sample at selected begin) ", - "SetBegin xxxxxx sample-startadress", - "SetLen ..xxxx sample-length ", - "Wait ..xxxx count (VBI''s) ", - "Loop xx/xxxx count/step ", - "Cont xx/xxxx macro-number/step ", - "-------------STOP----------------------", - "AddNote xx/xxxx note/detune ", - "SetNote xx/xxxx note/detune ", - "Reset Vibrato-Portamento-Envelope ", - "Portamento xx/../xx count/speed ", - "Vibrato xx/../xx speed/intensity ", - "AddVolume ....xx volume 00-3F ", - "SetVolume ....xx volume 00-3F ", - "Envelope xx/xx/xx speed/count/endvol", - "Loop key up xx/xxxx count/step ", - "AddBegin xx/xxxx count/add to start", - "AddLen ..xxxx add to sample-len ", - "DMAoff stop sample but no clear ", - "Wait key up ....xx count (VBI''s) ", - "Go submacro xx/xxxx macro-number/step ", - "--------Return to old macro------------", - "Setperiod ..xxxx DMA period ", - "Sampleloop ..xxxx relative adress ", - "-------Set one shot sample-------------", - "Wait on DMA ..xxxx count (Wavecycles)", - "Random play xx/xx/xx macro/speed/mode ", - "Splitkey xx/xxxx key/macrostep ", - "Splitvolume xx/xxxx volume/macrostep ", - "Addvol+note xx/fe/xx note/CONST./volume", - "SetPrevNote xx/xxxx note/detune ", - "Signal xx/xxxx signalnumber/value", - "Play macro xx/.x/xx macro/chan/detune ", - "SID setbeg xxxxxx sample-startadress", - "SID setlen xx/xxxx buflen/sourcelen ", - "SID op3 ofs xxxxxx offset ", - "SID op3 frq xx/xxxx speed/amplitude ", - "SID op2 ofs xxxxxx offset ", - "SID op2 frq xx/xxxx speed/amplitude ", - "SID op1 xx/xx/xx speed/amplitude/TC", - "SID stop xx.... flag (1=clear all)" - }; - - const byte *const macroData = (const byte * const)vptr; - if (macroData[0] < ARRAYSIZE(tableMacros)) - debug("%s %02X%02X%02X", tableMacros[macroData[0]], macroData[1], macroData[2], macroData[3]); - else - debug("Unknown Macro #%02X %02X%02X%02X", macroData[0], macroData[1], macroData[2], macroData[3]); -} - -void displayPatternstep(const void * const vptr) { - static const char *tablePatterns[] = { - "End --Next track step--", - "Loop[count / step.w]", - "Cont[patternno./ step.w]", - "Wait[count 00-FF--------", - "Stop--Stop this pattern-", - "Kup^-Set key up/channel]", - "Vibr[speed / rate.b]", - "Enve[speed /endvolume.b]", - "GsPt[patternno./ step.w]", - "RoPt-Return old pattern-", - "Fade[speed /endvolume.b]", - "PPat[patt./track+transp]", - "Lock---------ch./time.b]", - "Cue [number.b/ value.w]", - "Stop-Stop custompattern-", - "NOP!-no operation-------" - }; - - const byte * const patData = (const byte * const)vptr; - const byte command = patData[0]; - if (command < 0xF0) { // Playnote - const byte flags = command >> 6; // 0-1 means note+detune, 2 means wait, 3 means portamento? - const char *flagsSt[] = { "Note ", "Note ", "Wait ", "Porta" }; - debug("%s %02X%02X%02X%02X", flagsSt[flags], patData[0], patData[1], patData[2], patData[3]); - } else - debug("%s %02X%02X%02X",tablePatterns[command & 0xF], patData[1], patData[2], patData[3]); -} - -} // End of anonymous namespace -#endif - -#endif // #if defined(SOUND_MODS_TFMX_H) diff --git a/sound/mods/tfmx.h b/sound/mods/tfmx.h deleted file mode 100644 index b24df494cd..0000000000 --- a/sound/mods/tfmx.h +++ /dev/null @@ -1,284 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -// see if all engines using this class are DISABLED -#if !defined(ENABLE_SCUMM) - -// normal Header Guard -#elif !defined(SOUND_MODS_TFMX_H) -#define SOUND_MODS_TFMX_H - -#include "sound/mods/paula.h" - -namespace Audio { - -class Tfmx : public Paula { -public: - Tfmx(int rate, bool stereo); - virtual ~Tfmx(); - - /** - * Stops a playing Song (but leaves macros running) and optionally also stops the player - * - * @param stopAudio stops player and audio output - * @param dataSize number of bytes to be written - * @return the number of bytes which were actually written. - */ - void stopSong(bool stopAudio = true) { Common::StackLock lock(_mutex); stopSongImpl(stopAudio); } - /** - * Stops currently playing Song (if any) and cues up a new one. - * if stopAudio is specified, the player gets reset before starting the new song - * - * @param songPos index of Song to play - * @param stopAudio stops player and audio output - * @param dataSize number of bytes to be written - * @return the number of bytes which were actually written. - */ - void doSong(int songPos, bool stopAudio = false); - /** - * plays an effect from the sfx-table, does not start audio-playback. - * - * @param sfxIndex index of effect to play - * @param unlockChannel overwrite higher priority effects - * @return index of the channel which now queued up the effect. - * -1 in case the effect couldnt be queued up - */ - int doSfx(uint16 sfxIndex, bool unlockChannel = false); - /** - * stop a running macro channel - * - * @param channel index of effect to stop - */ - void stopMacroEffect(int channel); - - void doMacro(int note, int macro, int relVol = 0, int finetune = 0, int channelNo = 0); - int getTicks() const { return _playerCtx.tickCount; } - int getSongIndex() const { return _playerCtx.song; } - void setSignalPtr(uint16 *ptr, uint16 numSignals) { _playerCtx.signal = ptr; _playerCtx.numSignals = numSignals; } - void freeResources() { _deleteResource = true; freeResourceDataImpl(); } - bool load(Common::SeekableReadStream &musicData, Common::SeekableReadStream &sampleData, bool autoDelete = true); - void setModuleData(Tfmx &otherPlayer); - -protected: - void interrupt(); - -private: - enum { kPalDefaultCiaVal = 11822, kNtscDefaultCiaVal = 14320, kCiaBaseInterval = 0x1B51F8 }; - enum { kNumVoices = 4, kNumChannels = 8, kNumSubsongs = 32, kMaxPatternOffsets = 128, kMaxMacroOffsets = 128 }; - - struct MdatResource { - const byte *mdatAlloc; ///< allocated Block of Memory - const byte *mdatData; ///< Start of mdat-File, might point before mdatAlloc to correct Offset - uint32 mdatLen; - - uint16 headerFlags; -// uint32 headerUnknown; -// char textField[6 * 40]; - - struct Subsong { - uint16 songstart; ///< Index in Trackstep-Table - uint16 songend; ///< Last index in Trackstep-Table - uint16 tempo; - } subsong[kNumSubsongs]; - - uint32 trackstepOffset; ///< Offset in mdat - uint32 sfxTableOffset; - - uint32 patternOffset[kMaxPatternOffsets]; ///< Offset in mdat - uint32 macroOffset[kMaxMacroOffsets]; ///< Offset in mdat - - void boundaryCheck(const void *address, size_t accessLen = 1) const { - assert(mdatAlloc <= address && (const byte *)address + accessLen <= (const byte *)mdatData + mdatLen); - } - } const *_resource; - - struct SampleResource { - const int8 *sampleData; ///< The whole sample-File - uint32 sampleLen; - - void boundaryCheck(const void *address, size_t accessLen = 2) const { - assert(sampleData <= address && (const byte *)address + accessLen <= (const byte *)sampleData + sampleLen); - } - } _resourceSample; - - bool _deleteResource; - - bool hasResources() { - return _resource && _resource->mdatLen && _resourceSample.sampleLen; - } - - struct ChannelContext { - byte paulaChannel; - -// byte macroIndex; - uint16 macroWait; - uint32 macroOffset; - uint32 macroReturnOffset; - uint16 macroStep; - uint16 macroReturnStep; - uint8 macroLoopCount; - bool macroRun; - int8 macroSfxRun; ///< values are the folowing: -1 macro disabled, 0 macro init, 1 macro running - - uint32 customMacro; - uint8 customMacroIndex; - uint8 customMacroPrio; - - bool sfxLocked; - int16 sfxLockTime; - bool keyUp; - - bool deferWait; - uint16 dmaIntCount; - - uint32 sampleStart; - uint16 sampleLen; - uint16 refPeriod; - uint16 period; - - int8 volume; - uint8 relVol; - uint8 note; - uint8 prevNote; - int16 fineTune; // always a signextended byte - - uint8 portaSkip; - uint8 portaCount; - uint16 portaDelta; - uint16 portaValue; - - uint8 envSkip; - uint8 envCount; - uint8 envDelta; - int8 envEndVolume; - - uint8 vibLength; - uint8 vibCount; - int16 vibValue; - int8 vibDelta; - - uint8 addBeginLength; - uint8 addBeginCount; - int32 addBeginDelta; - } _channelCtx[kNumVoices]; - - struct PatternContext { - uint32 offset; // patternStart, Offset from mdat - uint32 savedOffset; // for subroutine calls - uint16 step; // distance from patternStart - uint16 savedStep; - - uint8 command; - int8 expose; - uint8 loopCount; - uint8 wait; ///< how many ticks to wait before next Command - } _patternCtx[kNumChannels]; - - struct TrackStepContext { - uint16 startInd; - uint16 stopInd; - uint16 posInd; - int16 loopCount; - } _trackCtx; - - struct PlayerContext { - int8 song; ///< >= 0 if Song is running (means process Patterns) - - uint16 patternCount; - uint16 patternSkip; ///< skip that amount of CIA-Interrupts - - int8 volume; ///< Master Volume - - uint8 fadeSkip; - uint8 fadeCount; - int8 fadeEndVolume; - int8 fadeDelta; - - int tickCount; - - uint16 *signal; - uint16 numSignals; - - bool stopWithLastPattern; ///< hack to automatically stop the whole player if no Pattern is running - } _playerCtx; - - const byte *getSfxPtr(uint16 index = 0) const { - const byte *sfxPtr = (const byte *)(_resource->mdatData + _resource->sfxTableOffset + index * 8); - - _resource->boundaryCheck(sfxPtr, 8); - return sfxPtr; - } - - const uint16 *getTrackPtr(uint16 trackstep = 0) const { - const uint16 *trackData = (const uint16 *)(_resource->mdatData + _resource->trackstepOffset + 16 * trackstep); - - _resource->boundaryCheck(trackData, 16); - return trackData; - } - - const uint32 *getPatternPtr(uint32 offset) const { - const uint32 *pattData = (const uint32 *)(_resource->mdatData + offset); - - _resource->boundaryCheck(pattData, 4); - return pattData; - } - - const uint32 *getMacroPtr(uint32 offset) const { - const uint32 *macroData = (const uint32 *)(_resource->mdatData + offset); - - _resource->boundaryCheck(macroData, 4); - return macroData; - } - - const int8 *getSamplePtr(const uint32 offset) const { - const int8 *sample = _resourceSample.sampleData + offset; - - _resourceSample.boundaryCheck(sample, 2); - return sample; - } - - static inline void initMacroProgramm(ChannelContext &channel); - static inline void clearEffects(ChannelContext &channel); - static inline void haltMacroProgramm(ChannelContext &channel); - static inline void unlockMacroChannel(ChannelContext &channel); - static inline void initPattern(PatternContext &pattern, uint8 cmd, int8 expose, uint32 offset); - void stopSongImpl(bool stopAudio = true); - static inline void setNoteMacro(ChannelContext &channel, uint note, int fineTune); - void initFadeCommand(const uint8 fadeTempo, const int8 endVol); - void setModuleData(const MdatResource *resource, const int8 *sampleData, uint32 sampleLen, bool autoDelete = true); - static const MdatResource *loadMdatFile(Common::SeekableReadStream &musicData); - static const int8 *loadSampleFile(uint32 &sampleLen, Common::SeekableReadStream &sampleStream); - void freeResourceDataImpl(); - void effects(ChannelContext &channel); - void macroRun(ChannelContext &channel); - void advancePatterns(); - bool patternRun(PatternContext &pattern); - bool trackRun(bool incStep = false); - void noteCommand(uint8 note, uint8 param1, uint8 param2, uint8 param3); -}; - -} // End of namespace Audio - -#endif // !defined(SOUND_MODS_TFMX_H) diff --git a/sound/module.mk b/sound/module.mk deleted file mode 100644 index 6cfa165a95..0000000000 --- a/sound/module.mk +++ /dev/null @@ -1,61 +0,0 @@ -MODULE := sound - -MODULE_OBJS := \ - audiostream.o \ - fmopl.o \ - mididrv.o \ - midiparser_smf.o \ - midiparser_xmidi.o \ - midiparser.o \ - mixer.o \ - mpu401.o \ - musicplugin.o \ - null.o \ - timestamp.o \ - decoders/adpcm.o \ - decoders/aiff.o \ - decoders/flac.o \ - decoders/iff_sound.o \ - decoders/mac_snd.o \ - decoders/mp3.o \ - decoders/raw.o \ - decoders/vag.o \ - decoders/voc.o \ - decoders/vorbis.o \ - decoders/wave.o \ - mods/infogrames.o \ - mods/maxtrax.o \ - mods/module.o \ - mods/protracker.o \ - mods/paula.o \ - mods/rjp1.o \ - mods/soundfx.o \ - mods/tfmx.o \ - softsynth/adlib.o \ - softsynth/cms.o \ - softsynth/opl/dbopl.o \ - softsynth/opl/dosbox.o \ - softsynth/opl/mame.o \ - softsynth/fmtowns_pc98/towns_audio.o \ - softsynth/fmtowns_pc98/towns_euphony.o \ - softsynth/fmtowns_pc98/towns_pc98_driver.o \ - softsynth/fmtowns_pc98/towns_pc98_fmsynth.o \ - softsynth/appleiigs.o \ - softsynth/ym2612.o \ - softsynth/fluidsynth.o \ - softsynth/mt32.o \ - softsynth/pcspk.o \ - softsynth/sid.o \ - softsynth/wave6581.o - -ifndef USE_ARM_SOUND_ASM -MODULE_OBJS += \ - rate.o -else -MODULE_OBJS += \ - rate_arm.o \ - rate_arm_asm.o -endif - -# Include common rules -include $(srcdir)/rules.mk diff --git a/sound/mpu401.cpp b/sound/mpu401.cpp deleted file mode 100644 index fe18a3ee85..0000000000 --- a/sound/mpu401.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#include "sound/mpu401.h" -#include "common/system.h" -#include "common/timer.h" -#include "common/util.h" // for ARRAYSIZE - -void MidiChannel_MPU401::init(MidiDriver *owner, byte channel) { - _owner = owner; - _channel = channel; - _allocated = false; -} - -bool MidiChannel_MPU401::allocate() { - if (_allocated) - return false; - return (_allocated = true); -} - -MidiDriver *MidiChannel_MPU401::device() { - return _owner; -} - -void MidiChannel_MPU401::send(uint32 b) { - _owner->send((b & 0xFFFFFFF0) | (_channel & 0xF)); -} - -void MidiChannel_MPU401::noteOff(byte note) { - _owner->send(note << 8 | 0x80 | _channel); -} - -void MidiChannel_MPU401::noteOn(byte note, byte velocity) { - _owner->send(velocity << 16 | note << 8 | 0x90 | _channel); -} - -void MidiChannel_MPU401::programChange(byte program) { - _owner->send(program << 8 | 0xC0 | _channel); -} - -void MidiChannel_MPU401::pitchBend(int16 bend) { - _owner->send((((bend + 0x2000) >> 7) & 0x7F) << 16 | ((bend + 0x2000) & 0x7F) << 8 | 0xE0 | _channel); -} - -void MidiChannel_MPU401::controlChange(byte control, byte value) { - _owner->send(value << 16 | control << 8 | 0xB0 | _channel); -} - -void MidiChannel_MPU401::pitchBendFactor(byte value) { - _owner->setPitchBendRange(_channel, value); -} - -void MidiChannel_MPU401::sysEx_customInstrument(uint32 type, const byte *instr) { - _owner->sysEx_customInstrument(_channel, type, instr); -} - -const char *MidiDriver::getErrorName(int error_code) { - static const char *const midi_errors[] = { - "No error", - "Cannot connect", - "Streaming not available", - "Device not available", - "Driver already open" - }; - - if ((uint)error_code >= ARRAYSIZE(midi_errors)) - return "Unknown Error"; - return midi_errors[error_code]; -} - -MidiDriver_MPU401::MidiDriver_MPU401() : - MidiDriver(), - _timer_proc(0), - _channel_mask(0xFFFF) // Permit all 16 channels by default -{ - - uint i; - for (i = 0; i < ARRAYSIZE(_midi_channels); ++i) { - _midi_channels[i].init(this, i); - } -} - -void MidiDriver_MPU401::close() { - if (_timer_proc) - g_system->getTimerManager()->removeTimerProc(_timer_proc); - _timer_proc = 0; - for (int i = 0; i < 16; ++i) - send(0x7B << 8 | 0xB0 | i); -} - -uint32 MidiDriver_MPU401::property(int prop, uint32 param) { - switch (prop) { - case PROP_CHANNEL_MASK: - _channel_mask = param & 0xFFFF; - return 1; - } - - return 0; -} - -MidiChannel *MidiDriver_MPU401::allocateChannel() { - MidiChannel_MPU401 *chan; - uint i; - - for (i = 0; i < ARRAYSIZE(_midi_channels); ++i) { - if (i == 9 || !(_channel_mask & (1 << i))) - continue; - chan = &_midi_channels[i]; - if (chan->allocate()) { - return chan; - } - } - return NULL; -} - -void MidiDriver_MPU401::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { - if (!_timer_proc || !timer_proc) { - if (_timer_proc) - g_system->getTimerManager()->removeTimerProc(_timer_proc); - _timer_proc = timer_proc; - if (timer_proc) - g_system->getTimerManager()->installTimerProc(timer_proc, 10000, timer_param); - } -} diff --git a/sound/mpu401.h b/sound/mpu401.h deleted file mode 100644 index 2253ab429c..0000000000 --- a/sound/mpu401.h +++ /dev/null @@ -1,92 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef SOUND_MPU401_H -#define SOUND_MPU401_H - -#include "sound/mididrv.h" - -//////////////////////////////////////// -// -// Common MPU401 implementation methods -// -//////////////////////////////////////// - -class MidiDriver_MPU401; - -class MidiChannel_MPU401 : public MidiChannel { - -private: - MidiDriver *_owner; - bool _allocated; - byte _channel; - -public: - MidiDriver *device(); - byte getNumber() { return _channel; } - void release() { _allocated = false; } - - void send(uint32 b); - - // Regular messages - void noteOff(byte note); - void noteOn(byte note, byte velocity); - void programChange(byte program); - void pitchBend(int16 bend); - - // Control Change messages - void controlChange(byte control, byte value); - void pitchBendFactor(byte value); - - // SysEx messages - void sysEx_customInstrument(uint32 type, const byte *instr); - - // Only to be called by the owner - void init(MidiDriver *owner, byte channel); - bool allocate(); -}; - - - -class MidiDriver_MPU401 : public MidiDriver { -private: - MidiChannel_MPU401 _midi_channels[16]; - Common::TimerManager::TimerProc _timer_proc; - uint16 _channel_mask; - -public: - MidiDriver_MPU401(); - - virtual void close(); - void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc); - uint32 getBaseTempo(void) { return 10000; } - uint32 property(int prop, uint32 param); - - MidiChannel *allocateChannel(); - MidiChannel *getPercussionChannel() { return &_midi_channels[9]; } -}; - - -#endif diff --git a/sound/musicplugin.cpp b/sound/musicplugin.cpp deleted file mode 100644 index 8078094616..0000000000 --- a/sound/musicplugin.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/musicplugin.h" -#include "common/hash-str.h" -#include "common/translation.h" - -MusicDevice::MusicDevice(MusicPluginObject const *musicPlugin, Common::String name, MusicType mt) : - _musicDriverName(_(musicPlugin->getName())), _musicDriverId(musicPlugin->getId()), - _name(_(name)), _type(mt) { -} - -Common::String MusicDevice::getCompleteName() { - Common::String name; - - if (_name.empty()) { - // Default device, just show the driver name - name = _musicDriverName; - } else { - // Show both device and driver names - name = _name; - name += " ["; - name += _musicDriverName; - name += "]"; - } - - return name; -} - -Common::String MusicDevice::getCompleteId() { - Common::String id = _musicDriverId; - if (!_name.empty()) { - id += "_"; - id += _name; - } - - return id; -} - -MidiDriver::DeviceHandle MusicDevice::getHandle() { - return (MidiDriver::DeviceHandle)Common::hashit(getCompleteId().c_str()); -} diff --git a/sound/musicplugin.h b/sound/musicplugin.h deleted file mode 100644 index bbb4ed778c..0000000000 --- a/sound/musicplugin.h +++ /dev/null @@ -1,125 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#ifndef SOUND_MUSICPLUGIN_H -#define SOUND_MUSICPLUGIN_H - -#include "base/plugins.h" -#include "sound/mididrv.h" -#include "common/list.h" - -class MusicPluginObject; - -/** - * Description of a Music device. Used to list the devices a Music driver - * can manage and their capabilities. - * A device with an empty name means the default device. - */ -class MusicDevice { -public: - MusicDevice(MusicPluginObject const *musicPlugin, Common::String name, MusicType mt); - - Common::String &getName() { return _name; } - Common::String &getMusicDriverName() { return _musicDriverName; } - Common::String &getMusicDriverId() { return _musicDriverId; } - MusicType getMusicType() { return _type; } - - /** - * Returns a user readable string that contains the name of the current - * device name (if it isn't the default one) and the name of the driver. - */ - Common::String getCompleteName(); - - /** - * Returns a user readable string that contains the name of the current - * device name (if it isn't the default one) and the id of the driver. - */ - Common::String getCompleteId(); - - MidiDriver::DeviceHandle getHandle(); - -private: - Common::String _name; - Common::String _musicDriverName; - Common::String _musicDriverId; - MusicType _type; -}; - -/** List of music devices. */ -typedef Common::List MusicDevices; - -/** - * A MusicPluginObject is essentially a factory for MidiDriver instances with - * the added ability of listing the available devices and their capabilities. - */ -class MusicPluginObject : public PluginObject { -public: - virtual ~MusicPluginObject() {} - - /** - * Returns a unique string identifier which will be used to save the - * selected MIDI driver to the config file. - */ - virtual const char *getId() const = 0; - - /** - * Returns a list of the available devices. - */ - virtual MusicDevices getDevices() const = 0; - - /** - * Tries to instantiate a MIDI Driver instance based on the device - * previously detected via MidiDriver::detectDevice() - * - * @param mididriver Pointer to a pointer which the MusicPluginObject sets - * to the newly create MidiDriver, or 0 in case of an error - * - * @param dev Pointer to a device to be used then creating the driver instance. - * Default value of zero for driver types without devices. - * - * @return a Common::Error describing the error which occurred, or kNoError - */ - virtual Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const = 0; -}; - - -// Music plugins - -typedef PluginSubclass MusicPlugin; - -/** - * Singleton class which manages all Music plugins. - */ -class MusicManager : public Common::Singleton { -private: - friend class Common::Singleton; - -public: - const MusicPlugin::List &getPlugins() const; -}; - -/** Convenience shortcut for accessing the Music manager. */ -#define MusicMan MusicManager::instance() - -#endif diff --git a/sound/null.cpp b/sound/null.cpp deleted file mode 100644 index 556b96c14c..0000000000 --- a/sound/null.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#include "sound/null.h" - -Common::Error NullMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const { - *mididriver = new MidiDriver_NULL(); - - return Common::kNoError; -} - -MusicDevices NullMusicPlugin::getDevices() const { - MusicDevices devices; - devices.push_back(MusicDevice(this, "", MT_NULL)); - return devices; -} - -class AutoMusicPlugin : public NullMusicPlugin { -public: - const char *getName() const { - return _s(""); - } - - const char *getId() const { - return "auto"; - } - MusicDevices getDevices() const; -}; - -MusicDevices AutoMusicPlugin::getDevices() const { - MusicDevices devices; - devices.push_back(MusicDevice(this, "", MT_AUTO)); - return devices; -} - -//#if PLUGIN_ENABLED_DYNAMIC(NULL) - //REGISTER_PLUGIN_DYNAMIC(NULL, PLUGIN_TYPE_MUSIC, NullMusicPlugin); -//#else - REGISTER_PLUGIN_STATIC(AUTO, PLUGIN_TYPE_MUSIC, AutoMusicPlugin); - REGISTER_PLUGIN_STATIC(NULL, PLUGIN_TYPE_MUSIC, NullMusicPlugin); -//#endif diff --git a/sound/null.h b/sound/null.h deleted file mode 100644 index d9343701fa..0000000000 --- a/sound/null.h +++ /dev/null @@ -1,56 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#ifndef SOUND_NULL_H -#define SOUND_NULL_H - -#include "sound/musicplugin.h" -#include "sound/mpu401.h" -#include "common/translation.h" - -/* NULL driver */ -class MidiDriver_NULL : public MidiDriver_MPU401 { -public: - int open() { return 0; } - void send(uint32 b) { } -}; - - -// Plugin interface - -class NullMusicPlugin : public MusicPluginObject { -public: - virtual const char *getName() const { - return _s("No music"); - } - - virtual const char *getId() const { - return "null"; - } - - virtual MusicDevices getDevices() const; - Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const; -}; - -#endif diff --git a/sound/rate.cpp b/sound/rate.cpp deleted file mode 100644 index 97521da3e0..0000000000 --- a/sound/rate.cpp +++ /dev/null @@ -1,358 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/* - * The code in this file is based on code with Copyright 1998 Fabrice Bellard - * Fabrice original code is part of SoX (http://sox.sourceforge.net). - * Max Horn adapted that code to the needs of ScummVM and rewrote it partial, - * in the process removing any use of floating point arithmetic. Various other - * improvements over the original code were made. - */ - -#include "sound/audiostream.h" -#include "sound/rate.h" -#include "sound/mixer.h" -#include "common/frac.h" -#include "common/util.h" - -namespace Audio { - - -/** - * The size of the intermediate input cache. Bigger values may increase - * performance, but only until some point (depends largely on cache size, - * target processor and various other factors), at which it will decrease - * again. - */ -#define INTERMEDIATE_BUFFER_SIZE 512 - - -/** - * Audio rate converter based on simple resampling. Used when no - * interpolation is required. - * - * Limited to sampling frequency <= 65535 Hz. - */ -template -class SimpleRateConverter : public RateConverter { -protected: - st_sample_t inBuf[INTERMEDIATE_BUFFER_SIZE]; - const st_sample_t *inPtr; - int inLen; - - /** position of how far output is ahead of input */ - /** Holds what would have been opos-ipos */ - long opos; - - /** fractional position increment in the output stream */ - long opos_inc; - -public: - SimpleRateConverter(st_rate_t inrate, st_rate_t outrate); - int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r); - int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { - return ST_SUCCESS; - } -}; - - -/* - * Prepare processing. - */ -template -SimpleRateConverter::SimpleRateConverter(st_rate_t inrate, st_rate_t outrate) { - if ((inrate % outrate) != 0) { - error("Input rate must be a multiple of output rate to use rate effect"); - } - - if (inrate >= 65536 || outrate >= 65536) { - error("rate effect can only handle rates < 65536"); - } - - opos = 1; - - /* increment */ - opos_inc = inrate / outrate; - - inLen = 0; -} - -/* - * Processed signed long samples from ibuf to obuf. - * Return number of sample pairs processed. - */ -template -int SimpleRateConverter::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { - st_sample_t *ostart, *oend; - - ostart = obuf; - oend = obuf + osamp * 2; - - while (obuf < oend) { - - // read enough input samples so that opos >= 0 - do { - // Check if we have to refill the buffer - if (inLen == 0) { - inPtr = inBuf; - inLen = input.readBuffer(inBuf, ARRAYSIZE(inBuf)); - if (inLen <= 0) - return (obuf - ostart) / 2; - } - inLen -= (stereo ? 2 : 1); - opos--; - if (opos >= 0) { - inPtr += (stereo ? 2 : 1); - } - } while (opos >= 0); - - st_sample_t out0, out1; - out0 = *inPtr++; - out1 = (stereo ? *inPtr++ : out0); - - // Increment output position - opos += opos_inc; - - // output left channel - clampedAdd(obuf[reverseStereo ], (out0 * (int)vol_l) / Audio::Mixer::kMaxMixerVolume); - - // output right channel - clampedAdd(obuf[reverseStereo ^ 1], (out1 * (int)vol_r) / Audio::Mixer::kMaxMixerVolume); - - obuf += 2; - } - return (obuf - ostart) / 2; -} - -/** - * Audio rate converter based on simple linear Interpolation. - * - * The use of fractional increment allows us to use no buffer. It - * avoid the problems at the end of the buffer we had with the old - * method which stored a possibly big buffer of size - * lcm(in_rate,out_rate). - * - * Limited to sampling frequency <= 65535 Hz. - */ - -template -class LinearRateConverter : public RateConverter { -protected: - st_sample_t inBuf[INTERMEDIATE_BUFFER_SIZE]; - const st_sample_t *inPtr; - int inLen; - - /** fractional position of the output stream in input stream unit */ - frac_t opos; - - /** fractional position increment in the output stream */ - frac_t opos_inc; - - /** last sample(s) in the input stream (left/right channel) */ - st_sample_t ilast0, ilast1; - /** current sample(s) in the input stream (left/right channel) */ - st_sample_t icur0, icur1; - -public: - LinearRateConverter(st_rate_t inrate, st_rate_t outrate); - int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r); - int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { - return ST_SUCCESS; - } -}; - - -/* - * Prepare processing. - */ -template -LinearRateConverter::LinearRateConverter(st_rate_t inrate, st_rate_t outrate) { - if (inrate >= 65536 || outrate >= 65536) { - error("rate effect can only handle rates < 65536"); - } - - opos = FRAC_ONE; - - // Compute the linear interpolation increment. - // This will overflow if inrate >= 2^16, and underflow if outrate >= 2^16. - // Also, if the quotient of the two rate becomes too small / too big, that - // would cause problems, but since we rarely scale from 1 to 65536 Hz or vice - // versa, I think we can live with that limitation ;-). - opos_inc = (inrate << FRAC_BITS) / outrate; - - ilast0 = ilast1 = 0; - icur0 = icur1 = 0; - - inLen = 0; -} - -/* - * Processed signed long samples from ibuf to obuf. - * Return number of sample pairs processed. - */ -template -int LinearRateConverter::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { - st_sample_t *ostart, *oend; - - ostart = obuf; - oend = obuf + osamp * 2; - - while (obuf < oend) { - - // read enough input samples so that opos < 0 - while ((frac_t)FRAC_ONE <= opos) { - // Check if we have to refill the buffer - if (inLen == 0) { - inPtr = inBuf; - inLen = input.readBuffer(inBuf, ARRAYSIZE(inBuf)); - if (inLen <= 0) - return (obuf - ostart) / 2; - } - inLen -= (stereo ? 2 : 1); - ilast0 = icur0; - icur0 = *inPtr++; - if (stereo) { - ilast1 = icur1; - icur1 = *inPtr++; - } - opos -= FRAC_ONE; - } - - // Loop as long as the outpos trails behind, and as long as there is - // still space in the output buffer. - while (opos < (frac_t)FRAC_ONE && obuf < oend) { - // interpolate - st_sample_t out0, out1; - out0 = (st_sample_t)(ilast0 + (((icur0 - ilast0) * opos + FRAC_HALF) >> FRAC_BITS)); - out1 = (stereo ? - (st_sample_t)(ilast1 + (((icur1 - ilast1) * opos + FRAC_HALF) >> FRAC_BITS)) : - out0); - - // output left channel - clampedAdd(obuf[reverseStereo ], (out0 * (int)vol_l) / Audio::Mixer::kMaxMixerVolume); - - // output right channel - clampedAdd(obuf[reverseStereo ^ 1], (out1 * (int)vol_r) / Audio::Mixer::kMaxMixerVolume); - - obuf += 2; - - // Increment output position - opos += opos_inc; - } - } - return (obuf - ostart) / 2; -} - - -#pragma mark - - - -/** - * Simple audio rate converter for the case that the inrate equals the outrate. - */ -template -class CopyRateConverter : public RateConverter { - st_sample_t *_buffer; - st_size_t _bufferSize; -public: - CopyRateConverter() : _buffer(0), _bufferSize(0) {} - ~CopyRateConverter() { - free(_buffer); - } - - virtual int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { - assert(input.isStereo() == stereo); - - st_sample_t *ptr; - st_size_t len; - - st_sample_t *ostart = obuf; - - if (stereo) - osamp *= 2; - - // Reallocate temp buffer, if necessary - if (osamp > _bufferSize) { - free(_buffer); - _buffer = (st_sample_t *)malloc(osamp * 2); - _bufferSize = osamp; - } - - // Read up to 'osamp' samples into our temporary buffer - len = input.readBuffer(_buffer, osamp); - - // Mix the data into the output buffer - ptr = _buffer; - for (; len > 0; len -= (stereo ? 2 : 1)) { - st_sample_t out0, out1; - out0 = *ptr++; - out1 = (stereo ? *ptr++ : out0); - - // output left channel - clampedAdd(obuf[reverseStereo ], (out0 * (int)vol_l) / Audio::Mixer::kMaxMixerVolume); - - // output right channel - clampedAdd(obuf[reverseStereo ^ 1], (out1 * (int)vol_r) / Audio::Mixer::kMaxMixerVolume); - - obuf += 2; - } - return (obuf - ostart) / 2; - } - - virtual int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { - return ST_SUCCESS; - } -}; - - -#pragma mark - - -template -RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate) { - if (inrate != outrate) { - if ((inrate % outrate) == 0) { - return new SimpleRateConverter(inrate, outrate); - } else { - return new LinearRateConverter(inrate, outrate); - } - } else { - return new CopyRateConverter(); - } -} - -/** - * Create and return a RateConverter object for the specified input and output rates. - */ -RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate, bool stereo, bool reverseStereo) { - if (stereo) { - if (reverseStereo) - return makeRateConverter(inrate, outrate); - else - return makeRateConverter(inrate, outrate); - } else - return makeRateConverter(inrate, outrate); -} - -} // End of namespace Audio diff --git a/sound/rate.h b/sound/rate.h deleted file mode 100644 index fb231e4c4a..0000000000 --- a/sound/rate.h +++ /dev/null @@ -1,90 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef SOUND_RATE_H -#define SOUND_RATE_H - -#include "common/scummsys.h" -#include "engines/engine.h" - -class AudioStream; - - -namespace Audio { - -typedef int16 st_sample_t; -typedef uint16 st_volume_t; -typedef uint32 st_size_t; -typedef uint32 st_rate_t; - -/* Minimum and maximum values a sample can hold. */ -enum { - ST_SAMPLE_MAX = 0x7fffL, - ST_SAMPLE_MIN = (-ST_SAMPLE_MAX - 1L) -}; - -enum { - ST_EOF = -1, - ST_SUCCESS = 0 -}; - -static inline void clampedAdd(int16& a, int b) { - register int val; -#ifdef OUTPUT_UNSIGNED_AUDIO - val = (a ^ 0x8000) + b; -#else - val = a + b; -#endif - - if (val > ST_SAMPLE_MAX) - val = ST_SAMPLE_MAX; - else if (val < ST_SAMPLE_MIN) - val = ST_SAMPLE_MIN; - -#ifdef OUTPUT_UNSIGNED_AUDIO - a = ((int16)val) ^ 0x8000; -#else - a = val; -#endif -} - -class RateConverter { -public: - RateConverter() {} - virtual ~RateConverter() {} - - /** - * @return Number of sample pairs written into the buffer. - */ - virtual int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) = 0; - - virtual int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) = 0; -}; - -RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate, bool stereo, bool reverseStereo = false); - -} // End of namespace Audio - -#endif diff --git a/sound/rate_arm.cpp b/sound/rate_arm.cpp deleted file mode 100644 index 63008fcb87..0000000000 --- a/sound/rate_arm.cpp +++ /dev/null @@ -1,465 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/* - * The code in this file, together with the rate_arm_asm.s file offers - * an ARM optimised version of the code in rate.cpp. The operation of this - * code should be identical to that of rate.cpp, but faster. The heavy - * lifting is done in the assembler file. - * - * To be as portable as possible we implement the core routines with C - * linkage in assembly, and implement the C++ routines that call into - * the C here. The C++ symbol mangling varies wildly between compilers, - * so this is the simplest way to ensure that the C/C++ combination should - * work on as many ARM based platforms as possible. - * - * Essentially the algorithm herein is the same as that in rate.cpp, so - * anyone seeking to understand this should attempt to understand that - * first. That code was based in turn on code with Copyright 1998 Fabrice - * Bellard - part of SoX (http://sox.sourceforge.net). - * Max Horn adapted that code to the needs of ScummVM and partially rewrote - * it, in the process removing any use of floating point arithmetic. Various - * other improvments over the original code were made. - */ - -#include "sound/audiostream.h" -#include "sound/rate.h" -#include "sound/mixer.h" -#include "common/util.h" - -//#define DEBUG_RATECONV - -namespace Audio { - -/** - * The precision of the fractional computations used by the rate converter. - * Normally you should never have to modify this value. - * This stuff is defined in common/frac.h, but we redefine it here as the - * ARM routine we call doesn't respect those definitions. - */ -#define FRAC_BITS 16 -#define FRAC_ONE (1< -class SimpleRateConverter : public RateConverter { -protected: - SimpleRateDetails sr; -public: - SimpleRateConverter(st_rate_t inrate, st_rate_t outrate); - int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r); - int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { - return (ST_SUCCESS); - } -}; - - -/* - * Prepare processing. - */ -template -SimpleRateConverter::SimpleRateConverter(st_rate_t inrate, st_rate_t outrate) { - if (inrate == outrate) { - error("Input and Output rates must be different to use rate effect"); - } - - if ((inrate % outrate) != 0) { - error("Input rate must be a multiple of Output rate to use rate effect"); - } - - if (inrate >= 65536 || outrate >= 65536) { - error("rate effect can only handle rates < 65536"); - } - - sr.opos = 1; - - /* increment */ - sr.opos_inc = inrate / outrate; - - sr.inLen = 0; -} - -extern "C" { -#ifndef IPHONE -#define ARM_SimpleRate_M _ARM_SimpleRate_M -#define ARM_SimpleRate_S _ARM_SimpleRate_S -#define ARM_SimpleRate_R _ARM_SimpleRate_R -#endif -} - -extern "C" st_sample_t *ARM_SimpleRate_M( - AudioStream &input, - int (*fn)(Audio::AudioStream&,int16*,int), - SimpleRateDetails *sr, - st_sample_t *obuf, - st_size_t osamp, - st_volume_t vol_l, - st_volume_t vol_r); - -extern "C" st_sample_t *ARM_SimpleRate_S( - AudioStream &input, - int (*fn)(Audio::AudioStream&,int16*,int), - SimpleRateDetails *sr, - st_sample_t *obuf, - st_size_t osamp, - st_volume_t vol_l, - st_volume_t vol_r); - -extern "C" st_sample_t *ARM_SimpleRate_R( - AudioStream &input, - int (*fn)(Audio::AudioStream&,int16*,int), - SimpleRateDetails *sr, - st_sample_t *obuf, - st_size_t osamp, - st_volume_t vol_l, - st_volume_t vol_r); - -extern "C" int SimpleRate_readFudge(Audio::AudioStream &input, - int16 *a, int b) -{ -#ifdef DEBUG_RATECONV - debug("Reading ptr=%x n%d", a, b); -#endif - return input.readBuffer(a, b); -} - -template -int SimpleRateConverter::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { - -#ifdef DEBUG_RATECONV - debug("Simple st=%d rev=%d", stereo, reverseStereo); -#endif - st_sample_t *ostart = obuf; - - if (!stereo) { - obuf = ARM_SimpleRate_M(input, - &SimpleRate_readFudge, - &sr, - obuf, osamp, vol_l, vol_r); - } else if (reverseStereo) { - obuf = ARM_SimpleRate_R(input, - &SimpleRate_readFudge, - &sr, - obuf, osamp, vol_l, vol_r); - } else { - obuf = ARM_SimpleRate_S(input, - &SimpleRate_readFudge, - &sr, - obuf, osamp, vol_l, vol_r); - } - return (obuf-ostart)/2; -} - -/** - * Audio rate converter based on simple linear Interpolation. - * - * The use of fractional increment allows us to use no buffer. It - * avoid the problems at the end of the buffer we had with the old - * method which stored a possibly big buffer of size - * lcm(in_rate,out_rate). - * - * Limited to sampling frequency <= 65535 Hz. - */ - -typedef struct { - const st_sample_t *inPtr; - int inLen; - - /** position of how far output is ahead of input */ - /** Holds what would have been opos-ipos<<16 + opos_frac */ - long opos; - - /** integer position increment in the output stream */ - long opos_inc; - - /** current sample(s) in the input stream (left/right channel) */ - st_sample_t icur[2]; - /** last sample(s) in the input stream (left/right channel) */ - /** Note, these are deliberately ints, not st_sample_t's */ - int32 ilast[2]; - - st_sample_t inBuf[INTERMEDIATE_BUFFER_SIZE]; -} LinearRateDetails; - -extern "C" { -#ifndef IPHONE -#define ARM_LinearRate_M _ARM_LinearRate_M -#define ARM_LinearRate_S _ARM_LinearRate_S -#define ARM_LinearRate_R _ARM_LinearRate_R -#endif -} - -extern "C" st_sample_t *ARM_LinearRate_M( - AudioStream &input, - int (*fn)(Audio::AudioStream&,int16*,int), - LinearRateDetails *lr, - st_sample_t *obuf, - st_size_t osamp, - st_volume_t vol_l, - st_volume_t vol_r); - -extern "C" st_sample_t *ARM_LinearRate_S( - AudioStream &input, - int (*fn)(Audio::AudioStream&,int16*,int), - LinearRateDetails *lr, - st_sample_t *obuf, - st_size_t osamp, - st_volume_t vol_l, - st_volume_t vol_r); - -extern "C" st_sample_t *ARM_LinearRate_R( - AudioStream &input, - int (*fn)(Audio::AudioStream&,int16*,int), - LinearRateDetails *lr, - st_sample_t *obuf, - st_size_t osamp, - st_volume_t vol_l, - st_volume_t vol_r); - -template -class LinearRateConverter : public RateConverter { -protected: - LinearRateDetails lr; - -public: - LinearRateConverter(st_rate_t inrate, st_rate_t outrate); - int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r); - int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { - return (ST_SUCCESS); - } -}; - - -/* - * Prepare processing. - */ -template -LinearRateConverter::LinearRateConverter(st_rate_t inrate, st_rate_t outrate) { - unsigned long incr; - - if (inrate == outrate) { - error("Input and Output rates must be different to use rate effect"); - } - - if (inrate >= 65536 || outrate >= 65536) { - error("rate effect can only handle rates < 65536"); - } - - lr.opos = FRAC_ONE; - - /* increment */ - incr = (inrate << FRAC_BITS) / outrate; - - lr.opos_inc = incr; - - lr.ilast[0] = lr.ilast[1] = 32768; - lr.icur[0] = lr.icur[1] = 0; - - lr.inLen = 0; -} - -/* - * Processed signed long samples from ibuf to obuf. - * Return number of sample pairs processed. - */ -template -int LinearRateConverter::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { - -#ifdef DEBUG_RATECONV - debug("Linear st=%d rev=%d", stereo, reverseStereo); -#endif - st_sample_t *ostart = obuf; - - if (!stereo) { - obuf = ARM_LinearRate_M(input, - &SimpleRate_readFudge, - &lr, - obuf, osamp, vol_l, vol_r); - } else if (reverseStereo) { - obuf = ARM_LinearRate_R(input, - &SimpleRate_readFudge, - &lr, - obuf, osamp, vol_l, vol_r); - } else { - obuf = ARM_LinearRate_S(input, - &SimpleRate_readFudge, - &lr, - obuf, osamp, vol_l, vol_r); - } - return (obuf-ostart)/2; -} - - -#pragma mark - - - -/** - * Simple audio rate converter for the case that the inrate equals the outrate. - */ -extern "C" { -#ifndef IPHONE -#define ARM_CopyRate_M _ARM_CopyRate_M -#define ARM_CopyRate_S _ARM_CopyRate_S -#define ARM_CopyRate_R _ARM_CopyRate_R -#endif -} - -extern "C" st_sample_t *ARM_CopyRate_M( - st_size_t len, - st_sample_t *obuf, - st_volume_t vol_l, - st_volume_t vol_r, - st_sample_t *_buffer); - -extern "C" st_sample_t *ARM_CopyRate_S( - st_size_t len, - st_sample_t *obuf, - st_volume_t vol_l, - st_volume_t vol_r, - st_sample_t *_buffer); - -extern "C" st_sample_t *ARM_CopyRate_R( - st_size_t len, - st_sample_t *obuf, - st_volume_t vol_l, - st_volume_t vol_r, - st_sample_t *_buffer); - - -template -class CopyRateConverter : public RateConverter { - st_sample_t *_buffer; - st_size_t _bufferSize; -public: - CopyRateConverter() : _buffer(0), _bufferSize(0) {} - ~CopyRateConverter() { - free(_buffer); - } - - virtual int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { - assert(input.isStereo() == stereo); - -#ifdef DEBUG_RATECONV - debug("Copy st=%d rev=%d", stereo, reverseStereo); -#endif - st_size_t len; - st_sample_t *ostart = obuf; - - if (stereo) - osamp *= 2; - - // Reallocate temp buffer, if necessary - if (osamp > _bufferSize) { - free(_buffer); - _buffer = (st_sample_t *)malloc(osamp * 2); - _bufferSize = osamp; - } - - // Read up to 'osamp' samples into our temporary buffer - len = input.readBuffer(_buffer, osamp); - if (len <= 0) - return 0; - - // Mix the data into the output buffer - if (stereo && reverseStereo) - obuf = ARM_CopyRate_R(len, obuf, vol_l, vol_r, _buffer); - else if (stereo) - obuf = ARM_CopyRate_S(len, obuf, vol_l, vol_r, _buffer); - else - obuf = ARM_CopyRate_M(len, obuf, vol_l, vol_r, _buffer); - - return (obuf-ostart)/2; - } - virtual int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { - return (ST_SUCCESS); - } -}; - - -#pragma mark - - - -/** - * Create and return a RateConverter object for the specified input and output rates. - */ -RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate, bool stereo, bool reverseStereo) { - if (inrate != outrate) { - if ((inrate % outrate) == 0) { - if (stereo) { - if (reverseStereo) - return new SimpleRateConverter(inrate, outrate); - else - return new SimpleRateConverter(inrate, outrate); - } else - return new SimpleRateConverter(inrate, outrate); - } else { - if (stereo) { - if (reverseStereo) - return new LinearRateConverter(inrate, outrate); - else - return new LinearRateConverter(inrate, outrate); - } else - return new LinearRateConverter(inrate, outrate); - } - } else { - if (stereo) { - if (reverseStereo) - return new CopyRateConverter(); - else - return new CopyRateConverter(); - } else - return new CopyRateConverter(); - } -} - -} // End of namespace Audio diff --git a/sound/rate_arm_asm.s b/sound/rate_arm_asm.s deleted file mode 100644 index 9431ae0649..0000000000 --- a/sound/rate_arm_asm.s +++ /dev/null @@ -1,687 +0,0 @@ -@ ScummVM - Graphic Adventure Engine -@ -@ ScummVM is the legal property of its developers, whose names -@ are too numerous to list here. Please refer to the COPYRIGHT -@ file distributed with this source distribution. -@ -@ This program is free software@ you can redistribute it and/or -@ modify it under the terms of the GNU General Public License -@ as published by the Free Software Foundation@ either version 2 -@ of the License, or (at your option) any later version. -@ -@ This program is distributed in the hope that it will be useful, -@ but WITHOUT ANY WARRANTY@ without even the implied warranty of -@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -@ GNU General Public License for more details. -@ -@ You should have received a copy of the GNU General Public License -@ along with this program@ if not, write to the Free Software -@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -@ -@ $URL$ -@ $Id$ -@ -@ @author Robin Watts (robin@wss.co.uk) -@ -@ This file, together with rate_arm.cpp, provides an ARM optimised version -@ of rate.cpp. The algorithm is essentially the same as that within rate.cpp -@ so to understand this file you should understand rate.cpp first. - - .text - - .global _ARM_CopyRate_M - .global _ARM_CopyRate_S - .global _ARM_CopyRate_R - .global _ARM_SimpleRate_M - .global _ARM_SimpleRate_S - .global _ARM_SimpleRate_R - .global _ARM_LinearRate_M - .global _ARM_LinearRate_S - .global _ARM_LinearRate_R - -_ARM_CopyRate_M: - @ r0 = len - @ r1 = obuf - @ r2 = vol_l - @ r3 = vol_r - @ <> = ptr - LDR r12,[r13] - STMFD r13!,{r4-r7,r14} - - MOV r14,#0 @ r14= 0 - ORR r2, r2, r2, LSL #8 @ r2 = vol_l as 16 bits - ORR r3, r3, r3, LSL #8 @ r3 = vol_r as 16 bits -CopyRate_M_loop: - LDRSH r5, [r12], #2 @ r5 = tmp0 = tmp1 = *ptr++ - LDRSH r6, [r1] @ r6 = obuf[0] - LDRSH r7, [r1, #2] @ r7 = obuf[1] - MUL r4, r2, r5 @ r4 = tmp0*vol_l - MUL r5, r3, r5 @ r5 = tmp1*vol_r - - ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l - RSCVS r6, r14,#0x80000000 @ Clamp r6 - ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r - RSCVS r7, r14,#0x80000000 @ Clamp r7 - - MOV r6, r6, LSR #16 @ Shift back to halfword - MOV r7, r7, LSR #16 @ Shift back to halfword - - STRH r6, [r1], #2 @ Store output value - STRH r7, [r1], #2 @ Store output value - - SUBS r0,r0,#1 @ len-- - BGT CopyRate_M_loop @ and loop - - MOV r0, r1 @ return obuf - - LDMFD r13!,{r4-r7,PC} - -_ARM_CopyRate_S: - @ r0 = len - @ r1 = obuf - @ r2 = vol_l - @ r3 = vol_r - @ <> = ptr - LDR r12,[r13] - STMFD r13!,{r4-r7,r14} - - MOV r14,#0 @ r14= 0 - ORR r2, r2, r2, LSL #8 @ r2 = vol_l as 16 bits - ORR r3, r3, r3, LSL #8 @ r3 = vol_r as 16 bits -CopyRate_S_loop: - LDRSH r4, [r12],#2 @ r4 = tmp0 = *ptr++ - LDRSH r5, [r12],#2 @ r5 = tmp1 = *ptr++ - LDRSH r6, [r1] @ r6 = obuf[0] - LDRSH r7, [r1,#2] @ r7 = obuf[1] - MUL r4, r2, r4 @ r5 = tmp0*vol_l - MUL r5, r3, r5 @ r6 = tmp1*vol_r - - ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l - RSCVS r6, r14,#0x80000000 @ Clamp r6 - ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r - RSCVS r7, r14,#0x80000000 @ Clamp r7 - - MOV r6, r6, LSR #16 @ Shift back to halfword - MOV r7, r7, LSR #16 @ Shift back to halfword - - STRH r6, [r1],#2 @ Store output value - STRH r7, [r1],#2 @ Store output value - - SUBS r0,r0,#2 @ len -= 2 - BGT CopyRate_S_loop @ and loop - - MOV r0, r1 @ return obuf - - LDMFD r13!,{r4-r7,PC} - -_ARM_CopyRate_R: - @ r0 = len - @ r1 = obuf - @ r2 = vol_l - @ r3 = vol_r - @ <> = ptr - LDR r12,[r13] - STMFD r13!,{r4-r7,r14} - - MOV r14,#0 @ r14= 0 - ORR r2, r2, r2, LSL #8 @ r2 = vol_l as 16 bits - ORR r3, r3, r3, LSL #8 @ r3 = vol_r as 16 bits -CopyRate_R_loop: - LDRSH r5, [r12],#2 @ r5 = tmp1 = *ptr++ - LDRSH r4, [r12],#2 @ r4 = tmp0 = *ptr++ - LDRSH r6, [r1] @ r6 = obuf[0] - LDRSH r7, [r1,#2] @ r7 = obuf[1] - MUL r4, r2, r4 @ r4 = tmp0*vol_l - MUL r5, r3, r5 @ r5 = tmp1*vol_r - - ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l - RSCVS r6, r14,#0x80000000 @ Clamp r6 - ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r - RSCVS r7, r14,#0x80000000 @ Clamp r7 - - MOV r6, r6, LSR #16 @ Shift back to halfword - MOV r7, r7, LSR #16 @ Shift back to halfword - - STRH r6, [r1],#2 @ Store output value - STRH r7, [r1],#2 @ Store output value - - SUBS r0,r0,#2 @ len -= 2 - BGT CopyRate_R_loop @ and loop - - MOV r0, r1 @ return obuf - - LDMFD r13!,{r4-r7,PC} - -_ARM_SimpleRate_M: - @ r0 = AudioStream &input - @ r1 = input.readBuffer - @ r2 = input->sr - @ r3 = obuf - @ <> = osamp - @ <> = vol_l - @ <> = vol_r - MOV r12,r13 - STMFD r13!,{r0-r2,r4-r8,r10-r11,r14} - LDMFD r12,{r11,r12,r14} @ r11= osamp - @ r12= vol_l - @ r14= vol_r - LDMIA r2,{r0,r1,r2,r8} @ r0 = inPtr - @ r1 = inLen - @ r2 = opos - @ r8 = opos_inc - CMP r11,#0 @ if (osamp <= 0) - BLE SimpleRate_M_end @ bale - MOV r10,#0 - ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits - ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits -SimpleRate_M_loop: - SUBS r1, r1, #1 @ r1 = inLen -= 1 - BLT SimpleRate_M_read - SUBS r2, r2, #1 @ r2 = opos-- - ADDGE r0, r0, #2 @ if (r2 >= 0) { sr.inPtr++ - BGE SimpleRate_M_loop @ and loop } -SimpleRate_M_read_return: - LDRSH r5, [r0],#2 @ r5 = tmp1 = *inPtr++ - LDRSH r6, [r3] @ r6 = obuf[0] - LDRSH r7, [r3,#2] @ r7 = obuf[1] - ADD r2, r2, r8 @ r2 = opos += opos_inc - MUL r4, r12,r5 @ r4 = tmp0*vol_l - MUL r5, r14,r5 @ r5 = tmp1*vol_r - - ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l - RSCVS r6, r10,#0x80000000 @ Clamp r6 - ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r - RSCVS r7, r10,#0x80000000 @ Clamp r7 - - MOV r6, r6, LSR #16 @ Shift back to halfword - MOV r7, r7, LSR #16 @ Shift back to halfword - - STRH r6, [r3],#2 @ Store output value - STRH r7, [r3],#2 @ Store output value - - SUBS r11,r11,#1 @ len-- - BGT SimpleRate_M_loop @ and loop -SimpleRate_M_end: - LDR r14,[r13,#8] @ r14 = sr - ADD r13,r13,#12 @ Skip over r0-r2 on stack - STMIA r14,{r0,r1,r2} @ Store back updated values - - MOV r0, r3 @ return obuf - - LDMFD r13!,{r4-r8,r10-r11,PC} -SimpleRate_M_read: - LDR r0, [r13,#8] @ r0 = sr (8 = 4*2) - ADD r0, r0, #16 @ r0 = inPtr = inBuf - STMFD r13!,{r0,r2-r3,r12,r14} - - MOV r1, r0 @ r1 = inBuf - LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5) - MOV r2, #512 @ r2 = ARRAYSIZE(inBuf) - - @ Calling back into C++ here. WinCE is fairly easy about such things - @ but other OS are more awkward. r9 is preserved for Symbian, and - @ we have 3+8+5 = 16 things on the stack (an even number). - MOV r14,PC - LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6) - SUBS r1, r0, #1 @ r1 = inLen-1 - LDMFD r13!,{r0,r2-r3,r12,r14} - BLT SimpleRate_M_end - SUBS r2, r2, #1 @ r2 = opos-- - ADDGE r0, r0, #2 @ if (r2 >= 0) { sr.inPtr++ - BGE SimpleRate_M_loop @ and loop } - B SimpleRate_M_read_return - - -_ARM_SimpleRate_S: - @ r0 = AudioStream &input - @ r1 = input.readBuffer - @ r2 = input->sr - @ r3 = obuf - @ <> = osamp - @ <> = vol_l - @ <> = vol_r - MOV r12,r13 - STMFD r13!,{r0-r2,r4-r8,r10-r11,r14} - LDMFD r12,{r11,r12,r14} @ r11= osamp - @ r12= vol_l - @ r14= vol_r - LDMIA r2,{r0,r1,r2,r8} @ r0 = inPtr - @ r1 = inLen - @ r2 = opos - @ r8 = opos_inc - CMP r11,#0 @ if (osamp <= 0) - BLE SimpleRate_S_end @ bale - MOV r10,#0 - ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits - ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits -SimpleRate_S_loop: - SUBS r1, r1, #2 @ r1 = inLen -= 2 - BLT SimpleRate_S_read - SUBS r2, r2, #1 @ r2 = opos-- - ADDGE r0, r0, #4 @ if (r2 >= 0) { sr.inPtr += 2 - BGE SimpleRate_S_loop @ and loop } -SimpleRate_S_read_return: - LDRSH r4, [r0],#2 @ r4 = tmp0 = *inPtr++ - LDRSH r5, [r0],#2 @ r5 = tmp1 = *inPtr++ - LDRSH r6, [r3] @ r6 = obuf[0] - LDRSH r7, [r3,#2] @ r7 = obuf[1] - ADD r2, r2, r8 @ r2 = opos += opos_inc - MUL r4, r12,r4 @ r5 = tmp0*vol_l - MUL r5, r14,r5 @ r6 = tmp1*vol_r - - ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l - RSCVS r6, r10,#0x80000000 @ Clamp r6 - ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r - RSCVS r7, r10,#0x80000000 @ Clamp r7 - - MOV r6, r6, LSR #16 @ Shift back to halfword - MOV r7, r7, LSR #16 @ Shift back to halfword - - STRH r6, [r3],#2 @ Store output value - STRH r7, [r3],#2 @ Store output value - - SUBS r11,r11,#1 @ osamp-- - BGT SimpleRate_S_loop @ and loop -SimpleRate_S_end: - LDR r14,[r13,#8] @ r14 = sr - ADD r13,r13,#12 @ skip over r0-r2 on stack - STMIA r14,{r0,r1,r2} @ store back updated values - MOV r0, r3 @ return obuf - LDMFD r13!,{r4-r8,r10-r11,PC} -SimpleRate_S_read: - LDR r0, [r13,#8] @ r0 = sr (8 = 4*2) - ADD r0, r0, #16 @ r0 = inPtr = inBuf - STMFD r13!,{r0,r2-r3,r12,r14} - MOV r1, r0 @ r1 = inBuf - LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5) - MOV r2, #512 @ r2 = ARRAYSIZE(inBuf) - - @ Calling back into C++ here. WinCE is fairly easy about such things - @ but other OS are more awkward. r9 is preserved for Symbian, and - @ we have 3+8+5 = 16 things on the stack (an even number). - MOV r14,PC - LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6) - SUBS r1, r0, #2 @ r1 = inLen-2 - LDMFD r13!,{r0,r2-r3,r12,r14} - BLT SimpleRate_S_end - SUBS r2, r2, #1 @ r2 = opos-- - ADDGE r0, r0, #4 @ if (r2 >= 0) { sr.inPtr += 2 - BGE SimpleRate_S_loop @ and loop } - B SimpleRate_S_read_return - - - -_ARM_SimpleRate_R: - @ r0 = AudioStream &input - @ r1 = input.readBuffer - @ r2 = input->sr - @ r3 = obuf - @ <> = osamp - @ <> = vol_l - @ <> = vol_r - MOV r12,r13 - STMFD r13!,{r0-r2,r4-r8,r10-r11,r14} - LDMFD r12,{r11,r12,r14} @ r11= osamp - @ r12= vol_l - @ r14= vol_r - LDMIA r2,{r0,r1,r2,r8} @ r0 = inPtr - @ r1 = inLen - @ r2 = opos - @ r8 = opos_inc - CMP r11,#0 @ if (osamp <= 0) - BLE SimpleRate_R_end @ bale - MOV r10,#0 - ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits - ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits -SimpleRate_R_loop: - SUBS r1, r1, #2 @ r1 = inLen -= 2 - BLT SimpleRate_R_read - SUBS r2, r2, #1 @ r2 = opos-- - ADDGE r0, r0, #4 @ if (r2 >= 0) { sr.inPtr += 2 - BGE SimpleRate_R_loop @ and loop } -SimpleRate_R_read_return: - LDRSH r5, [r0],#2 @ r5 = tmp0 = *inPtr++ - LDRSH r4, [r0],#2 @ r4 = tmp1 = *inPtr++ - LDRSH r6, [r3] @ r6 = obuf[0] - LDRSH r7, [r3,#2] @ r7 = obuf[1] - ADD r2, r2, r8 @ r2 = opos += opos_inc - MUL r4, r12,r4 @ r5 = tmp0*vol_l - MUL r5, r14,r5 @ r6 = tmp1*vol_r - - ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l - RSCVS r6, r10,#0x80000000 @ Clamp r6 - ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r - RSCVS r7, r10,#0x80000000 @ Clamp r7 - - MOV r6, r6, LSR #16 @ Shift back to halfword - MOV r7, r7, LSR #16 @ Shift back to halfword - - STRH r6, [r3],#2 @ Store output value - STRH r7, [r3],#2 @ Store output value - - SUBS r11,r11,#1 @ osamp-- - BGT SimpleRate_R_loop @ and loop -SimpleRate_R_end: - LDR r14,[r13,#8] @ r14 = sr - ADD r13,r13,#12 @ Skip over r0-r2 on stack - STMIA r14,{r0,r1,r2} @ Store back updated values - MOV r0, r3 @ return obuf - LDMFD r13!,{r4-r8,r10-r11,PC} -SimpleRate_R_read: - LDR r0, [r13,#8] @ r0 = sr (8 = 4*2) - ADD r0, r0, #16 @ r0 = inPtr = inBuf - STMFD r13!,{r0,r2-r3,r12,r14} - - MOV r1, r0 @ r1 = inBuf - LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5) - MOV r2, #512 @ r2 = ARRAYSIZE(inBuf) - - @ Calling back into C++ here. WinCE is fairly easy about such things - @ but other OS are more awkward. r9 is preserved for Symbian, and - @ we have 3+8+5 = 16 things on the stack (an even number). - MOV r14,PC - LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6) - SUBS r1, r0, #2 @ r1 = inLen-2 - LDMFD r13!,{r0,r2-r3,r12,r14} - BLT SimpleRate_R_end - SUBS r2, r2, #1 @ r2 = opos-- - ADDGE r0, r0, #4 @ if (r2 >= 0) { sr.inPtr += 2 - BGE SimpleRate_R_loop @ and loop } - B SimpleRate_R_read_return - - -_ARM_LinearRate_M: - @ r0 = AudioStream &input - @ r1 = input.readBuffer - @ r2 = input->sr - @ r3 = obuf - @ <> = osamp - @ <> = vol_l - @ <> = vol_r - MOV r12,r13 - STMFD r13!,{r0-r1,r4-r11,r14} - LDMFD r12,{r11,r12,r14} @ r11= osamp - @ r12= vol_l - @ r14= vol_r - LDMIA r2,{r0,r1,r8} @ r0 = inPtr - @ r1 = inLen - @ r8 = opos - MOV r10,#0 - CMP r11,#0 @ if (osamp <= 0) - BLE LinearRate_M_end @ bale - ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits - ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits - CMP r1,#0 - BGT LinearRate_M_part2 - - @ part1 - read input samples -LinearRate_M_loop: - SUBS r1, r1, #1 @ r1 = inLen -= 1 - BLT LinearRate_M_read -LinearRate_M_read_return: - LDRH r4, [r2, #16] @ r4 = icur[0] - LDRSH r5, [r0],#2 @ r5 = tmp1 = *inPtr++ - SUBS r8, r8, #65536 @ r8 = opos-- - STRH r4, [r2,#22] @ ilast[0] = icur[0] - STRH r5, [r2,#16] @ icur[0] = tmp1 - BGE LinearRate_M_loop - - @ part2 - form output samples -LinearRate_M_part2: - @ We are guaranteed that opos < 0 here - LDR r6, [r2,#20] @ r6 = ilast[0]<<16 + 32768 - LDRSH r5, [r2,#16] @ r5 = icur[0] - MOV r4, r8, LSL #16 - MOV r4, r4, LSR #16 - SUB r5, r5, r6, ASR #16 @ r5 = icur[0] - ilast[0] - MLA r6, r4, r5, r6 @ r6 = (icur[0]-ilast[0])*opos_frac+ilast[0] - - LDRSH r4, [r3] @ r4 = obuf[0] - LDRSH r5, [r3,#2] @ r5 = obuf[1] - MOV r6, r6, ASR #16 @ r6 = tmp0 = tmp1 >>= 16 - MUL r7, r12,r6 @ r7 = tmp0*vol_l - MUL r6, r14,r6 @ r6 = tmp1*vol_r - - ADDS r7, r7, r4, LSL #16 @ r7 = obuf[0]<<16 + tmp0*vol_l - RSCVS r7, r10, #0x80000000 @ Clamp r7 - ADDS r6, r6, r5, LSL #16 @ r6 = obuf[1]<<16 + tmp1*vol_r - RSCVS r6, r10, #0x80000000 @ Clamp r6 - - MOV r7, r7, LSR #16 @ Shift back to halfword - MOV r6, r6, LSR #16 @ Shift back to halfword - - LDR r5, [r2,#12] @ r5 = opos_inc - STRH r7, [r3],#2 @ Store output value - STRH r6, [r3],#2 @ Store output value - SUBS r11, r11,#1 @ osamp-- - BLE LinearRate_M_end @ end if needed - - ADDS r8, r8, r5 @ r8 = opos += opos_inc - BLT LinearRate_M_part2 - B LinearRate_M_loop -LinearRate_M_end: - ADD r13,r13,#8 - STMIA r2,{r0,r1,r8} - MOV r0, r3 @ return obuf - LDMFD r13!,{r4-r11,PC} -LinearRate_M_read: - ADD r0, r2, #28 @ r0 = inPtr = inBuf - STMFD r13!,{r0,r2-r3,r12,r14} - - MOV r1, r0 @ r1 = inBuf - LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5) - MOV r2, #512 @ r2 = ARRAYSIZE(inBuf) - - @ Calling back into C++ here. WinCE is fairly easy about such things - @ but other OS are more awkward. r9 is preserved for Symbian, and - @ we have 2+9+5 = 16 things on the stack (an even number). - MOV r14,PC - LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6) - SUBS r1, r0, #1 @ r1 = inLen-1 - LDMFD r13!,{r0,r2-r3,r12,r14} - BLT LinearRate_M_end - B LinearRate_M_read_return - -_ARM_LinearRate_S: - @ r0 = AudioStream &input - @ r1 = input.readBuffer - @ r2 = input->sr - @ r3 = obuf - @ <> = osamp - @ <> = vol_l - @ <> = vol_r - MOV r12,r13 - STMFD r13!,{r0-r1,r4-r11,r14} - LDMFD r12,{r11,r12,r14} @ r11= osamp - @ r12= vol_l - @ r14= vol_r - LDMIA r2,{r0,r1,r8} @ r0 = inPtr - @ r1 = inLen - @ r8 = opos - CMP r11,#0 @ if (osamp <= 0) - BLE LinearRate_S_end @ bale - ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits - ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits - CMP r1,#0 - BGT LinearRate_S_part2 - - @ part1 - read input samples -LinearRate_S_loop: - SUBS r1, r1, #2 @ r1 = inLen -= 2 - BLT LinearRate_S_read -LinearRate_S_read_return: - LDR r10,[r2, #16] @ r10= icur[0,1] - LDRSH r5, [r0],#2 @ r5 = tmp0 = *inPtr++ - LDRSH r6, [r0],#2 @ r5 = tmp1 = *inPtr++ - SUBS r8, r8, #65536 @ r8 = opos-- - STRH r10,[r2,#22] @ ilast[0] = icur[0] - MOV r10,r10,LSR #16 - STRH r10,[r2,#26] @ ilast[1] = icur[1] - STRH r5, [r2,#16] @ icur[0] = tmp0 - STRH r6, [r2,#18] @ icur[1] = tmp1 - BGE LinearRate_S_loop - - @ part2 - form output samples -LinearRate_S_part2: - @ We are guaranteed that opos < 0 here - LDR r6, [r2,#20] @ r6 = ilast[0]<<16 + 32768 - LDRSH r5, [r2,#16] @ r5 = icur[0] - MOV r4, r8, LSL #16 - MOV r4, r4, LSR #16 - SUB r5, r5, r6, ASR #16 @ r5 = icur[0] - ilast[0] - MLA r6, r4, r5, r6 @ r6 = (icur[0]-ilast[0])*opos_frac+ilast[0] - - LDR r7, [r2,#24] @ r7 = ilast[1]<<16 + 32768 - LDRSH r5, [r2,#18] @ r5 = icur[1] - LDRSH r10,[r3] @ r10= obuf[0] - MOV r6, r6, ASR #16 @ r6 = tmp1 >>= 16 - SUB r5, r5, r7, ASR #16 @ r5 = icur[1] - ilast[1] - MLA r7, r4, r5, r7 @ r7 = (icur[1]-ilast[1])*opos_frac+ilast[1] - - LDRSH r5, [r3,#2] @ r5 = obuf[1] - MOV r7, r7, ASR #16 @ r7 = tmp0 >>= 16 - MUL r7, r12,r7 @ r7 = tmp0*vol_l - MUL r6, r14,r6 @ r6 = tmp1*vol_r - - ADDS r7, r7, r10, LSL #16 @ r7 = obuf[0]<<16 + tmp0*vol_l - MOV r4, #0 - RSCVS r7, r4, #0x80000000 @ Clamp r7 - ADDS r6, r6, r5, LSL #16 @ r6 = obuf[1]<<16 + tmp1*vol_r - RSCVS r6, r4, #0x80000000 @ Clamp r6 - - MOV r7, r7, LSR #16 @ Shift back to halfword - MOV r6, r6, LSR #16 @ Shift back to halfword - - LDR r5, [r2,#12] @ r5 = opos_inc - STRH r7, [r3],#2 @ Store output value - STRH r6, [r3],#2 @ Store output value - SUBS r11, r11,#1 @ osamp-- - BLE LinearRate_S_end @ and loop - - ADDS r8, r8, r5 @ r8 = opos += opos_inc - BLT LinearRate_S_part2 - B LinearRate_S_loop -LinearRate_S_end: - ADD r13,r13,#8 - STMIA r2,{r0,r1,r8} - MOV r0, r3 @ return obuf - LDMFD r13!,{r4-r11,PC} -LinearRate_S_read: - ADD r0, r2, #28 @ r0 = inPtr = inBuf - STMFD r13!,{r0,r2-r3,r12,r14} - - MOV r1, r0 @ r1 = inBuf - LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5) - MOV r2, #512 @ r2 = ARRAYSIZE(inBuf) - - @ Calling back into C++ here. WinCE is fairly easy about such things - @ but other OS are more awkward. r9 is preserved for Symbian, and - @ we have 2+9+5 = 16 things on the stack (an even number). - MOV r14,PC - LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6) - SUBS r1, r0, #2 @ r1 = inLen-2 - LDMFD r13!,{r0,r2-r3,r12,r14} - BLT LinearRate_S_end - B LinearRate_S_read_return - -_ARM_LinearRate_R: - @ r0 = AudioStream &input - @ r1 = input.readBuffer - @ r2 = input->sr - @ r3 = obuf - @ <> = osamp - @ <> = vol_l - @ <> = vol_r - MOV r12,r13 - STMFD r13!,{r0-r1,r4-r11,r14} - LDMFD r12,{r11,r12,r14} @ r11= osamp - @ r12= vol_l - @ r14= vol_r - LDMIA r2,{r0,r1,r8} @ r0 = inPtr - @ r1 = inLen - @ r8 = opos - CMP r11,#0 @ if (osamp <= 0) - BLE LinearRate_R_end @ bale - ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits - ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits - CMP r1,#0 - BGT LinearRate_R_part2 - - @ part1 - read input samples -LinearRate_R_loop: - SUBS r1, r1, #2 @ r1 = inLen -= 2 - BLT LinearRate_R_read -LinearRate_R_read_return: - LDR r10,[r2, #16] @ r10= icur[0,1] - LDRSH r5, [r0],#2 @ r5 = tmp0 = *inPtr++ - LDRSH r6, [r0],#2 @ r5 = tmp1 = *inPtr++ - SUBS r8, r8, #65536 @ r8 = opos-- - STRH r10,[r2,#22] @ ilast[0] = icur[0] - MOV r10,r10,LSR #16 - STRH r10,[r2,#22] @ ilast[1] = icur[1] - STRH r5, [r2,#16] @ icur[0] = tmp0 - STRH r6, [r2,#18] @ icur[1] = tmp1 - BGE LinearRate_R_loop - - @ part2 - form output samples -LinearRate_R_part2: - @ We are guaranteed that opos < 0 here - LDR r6, [r2,#20] @ r6 = ilast[0] - LDRSH r5, [r2,#16] @ r5 = icur[0] - MOV r4, r8, LSL #16 - MOV r4, r4, LSR #16 - SUB r5, r5, r6, ASR #16 @ r5 = icur[0] - ilast[0] - MLA r6, r4, r5, r6 @ r6 = (icur[0]-ilast[0])*opos_frac+ilast[0] - - LDR r7, [r2,#24] @ r7 = ilast[1] - LDRSH r5, [r2,#18] @ r5 = icur[1] - LDR r10,[r3] @ r10= obuf[0] - MOV r6, r6, ASR #16 @ r6 = tmp1 >>= 16 - SUB r5, r5, r7, ASR #16 @ r5 = icur[1] - ilast[1] - MLA r7, r4, r5, r7 @ r7 = (icur[1]-ilast[1])*opos_frac+ilast[1] - - LDRSH r5, [r3,#2] @ r5 = obuf[1] - MOV r7, r7, ASR #16 @ r7 = tmp0 >>= 16 - MUL r7, r12,r7 @ r7 = tmp0*vol_l - MUL r6, r14,r6 @ r6 = tmp1*vol_r - - ADDS r7, r7, r10, LSL #16 @ r7 = obuf[0]<<16 + tmp0*vol_l - MOV r4, #0 - RSCVS r7, r4, #0x80000000 @ Clamp r7 - ADDS r6, r6, r5, LSL #16 @ r6 = obuf[1]<<16 + tmp1*vol_r - RSCVS r6, r4, #0x80000000 @ Clamp r6 - - MOV r7, r7, LSR #16 @ Shift back to halfword - MOV r6, r6, LSR #16 @ Shift back to halfword - - LDR r5, [r2,#12] @ r5 = opos_inc - STRH r6, [r3],#2 @ Store output value - STRH r7, [r3],#2 @ Store output value - SUBS r11, r11,#1 @ osamp-- - BLE LinearRate_R_end @ and loop - - ADDS r8, r8, r5 @ r8 = opos += opos_inc - BLT LinearRate_R_part2 - B LinearRate_R_loop -LinearRate_R_end: - ADD r13,r13,#8 - STMIA r2,{r0,r1,r8} - MOV r0, r3 @ return obuf - LDMFD r13!,{r4-r11,PC} -LinearRate_R_read: - ADD r0, r2, #28 @ r0 = inPtr = inBuf - STMFD r13!,{r0,r2-r3,r12,r14} - - MOV r1, r0 @ r1 = inBuf - LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5) - MOV r2, #512 @ r2 = ARRAYSIZE(inBuf) - - @ Calling back into C++ here. WinCE is fairly easy about such things - @ but other OS are more awkward. r9 is preserved for Symbian, and - @ we have 2+9+5 = 16 things on the stack (an even number). - MOV r14,PC - LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6) - SUBS r1, r0, #2 @ r1 = inLen-2 - LDMFD r13!,{r0,r2-r3,r12,r14} - BLT LinearRate_R_end - B LinearRate_R_read_return diff --git a/sound/softsynth/adlib.cpp b/sound/softsynth/adlib.cpp deleted file mode 100644 index ffb359e816..0000000000 --- a/sound/softsynth/adlib.cpp +++ /dev/null @@ -1,1617 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#include "sound/softsynth/emumidi.h" -#include "common/debug.h" -#include "common/util.h" -#include "sound/fmopl.h" -#include "sound/musicplugin.h" -#include "common/translation.h" - -#ifdef DEBUG_ADLIB -static int tick; -#endif - -class MidiDriver_ADLIB; -struct AdLibVoice; - -// We use packing for the following two structs, because the code -// does simply copy them over from byte streams, without any -// serialization. Check AdLibPart::sysEx_customInstrument for an -// example of this. -// -// It might be very well possible, that none of the compilers we support -// add any padding bytes at all, since the structs contain only variables -// of the type 'byte'. But better safe than sorry. -#include "common/pack-start.h" -struct InstrumentExtra { - byte a, b, c, d, e, f, g, h; -} PACKED_STRUCT; - -struct AdLibInstrument { - byte mod_characteristic; - byte mod_scalingOutputLevel; - byte mod_attackDecay; - byte mod_sustainRelease; - byte mod_waveformSelect; - byte car_characteristic; - byte car_scalingOutputLevel; - byte car_attackDecay; - byte car_sustainRelease; - byte car_waveformSelect; - byte feedback; - byte flags_a; - InstrumentExtra extra_a; - byte flags_b; - InstrumentExtra extra_b; - byte duration; - - AdLibInstrument() { memset(this, 0, sizeof(AdLibInstrument)); } -} PACKED_STRUCT; -#include "common/pack-end.h" - -class AdLibPart : public MidiChannel { - friend class MidiDriver_ADLIB; - -protected: -// AdLibPart *_prev, *_next; - AdLibVoice *_voice; - int16 _pitchbend; - byte _pitchbend_factor; - int8 _transpose_eff; - byte _vol_eff; - int8 _detune_eff; - byte _modwheel; - bool _pedal; - byte _program; - byte _pri_eff; - AdLibInstrument _part_instr; - -protected: - MidiDriver_ADLIB *_owner; - bool _allocated; - byte _channel; - - void init(MidiDriver_ADLIB *owner, byte channel); - void allocate() { _allocated = true; } - -public: - AdLibPart() { - _voice = 0; - _pitchbend = 0; - _pitchbend_factor = 2; - _transpose_eff = 0; - _vol_eff = 0; - _detune_eff = 0; - _modwheel = 0; - _pedal = 0; - _program = 0; - _pri_eff = 0; - - _owner = 0; - _allocated = false; - _channel = 0; - } - - MidiDriver *device(); - byte getNumber() { return _channel; } - void release() { _allocated = false; } - - void send (uint32 b); - - // Regular messages - void noteOff(byte note); - void noteOn(byte note, byte velocity); - void programChange(byte program); - void pitchBend(int16 bend); - - // Control Change messages - void controlChange(byte control, byte value); - void modulationWheel(byte value); - void volume(byte value); - void panPosition(byte value) { return; } // Not supported - void pitchBendFactor(byte value); - void detune(byte value); - void priority(byte value); - void sustain(bool value); - void effectLevel(byte value) { return; } // Not supported - void chorusLevel(byte value) { return; } // Not supported - void allNotesOff(); - - // SysEx messages - void sysEx_customInstrument(uint32 type, const byte *instr); -}; - -// FYI (Jamieson630) -// It is assumed that any invocation to AdLibPercussionChannel -// will be done through the MidiChannel base class as opposed to the -// AdLibPart base class. If this were NOT the case, all the functions -// listed below would need to be virtual in AdLibPart as well as MidiChannel. -class AdLibPercussionChannel : public AdLibPart { - friend class MidiDriver_ADLIB; - -protected: - void init(MidiDriver_ADLIB *owner, byte channel); - -public: - ~AdLibPercussionChannel(); - - void noteOff(byte note); - void noteOn(byte note, byte velocity); - void programChange(byte program) { } - void pitchBend(int16 bend) { } - - // Control Change messages - void modulationWheel(byte value) { } - void pitchBendFactor(byte value) { } - void detune(byte value) { } - void priority(byte value) { } - void sustain(bool value) { } - - // SysEx messages - void sysEx_customInstrument(uint32 type, const byte *instr); - -private: - byte _notes[256]; - AdLibInstrument *_customInstruments[256]; -}; - -struct Struct10 { - byte active; - int16 cur_val; - int16 count; - uint16 max_value; - int16 start_value; - byte loop; - byte table_a[4]; - byte table_b[4]; - int8 unk3; - int8 modwheel; - int8 modwheel_last; - uint16 speed_lo_max; - uint16 num_steps; - int16 speed_hi; - int8 direction; - uint16 speed_lo; - uint16 speed_lo_counter; -}; - -struct Struct11 { - int16 modify_val; - byte param, flag0x40, flag0x10; - Struct10 *s10; -}; - -struct AdLibVoice { - AdLibPart *_part; - AdLibVoice *_next, *_prev; - byte _waitforpedal; - byte _note; - byte _channel; - byte _twochan; - byte _vol_1, _vol_2; - int16 _duration; - - Struct10 _s10a; - Struct11 _s11a; - Struct10 _s10b; - Struct11 _s11b; - - AdLibVoice() { memset(this, 0, sizeof(AdLibVoice)); } -}; - -struct AdLibSetParams { - byte a, b, c, d; -}; - -static const byte channel_mappings[9] = { - 0, 1, 2, 8, - 9, 10, 16, 17, - 18 -}; - -static const byte channel_mappings_2[9] = { - 3, 4, 5, 11, - 12, 13, 19, 20, - 21 -}; - -static const AdLibSetParams adlib_setparam_table[] = { - {0x40, 0, 63, 63}, // level - {0xE0, 2, 0, 0}, // unused - {0x40, 6, 192, 0}, // level key scaling - {0x20, 0, 15, 0}, // modulator frequency multiple - {0x60, 4, 240, 15}, // attack rate - {0x60, 0, 15, 15}, // decay rate - {0x80, 4, 240, 15}, // sustain level - {0x80, 0, 15, 15}, // release rate - {0xE0, 0, 3, 0}, // waveformSelect select - {0x20, 7, 128, 0}, // amp mod - {0x20, 6, 64, 0}, // vib - {0x20, 5, 32, 0}, // eg typ - {0x20, 4, 16, 0}, // ksr - {0xC0, 0, 1, 0}, // decay alg - {0xC0, 1, 14, 0} // feedback -}; - -static const byte param_table_1[16] = { - 29, 28, 27, 0, - 3, 4, 7, 8, - 13, 16, 17, 20, - 21, 30, 31, 0 -}; - -static const uint16 maxval_table[16] = { - 0x2FF, 0x1F, 0x7, 0x3F, - 0x0F, 0x0F, 0x0F, 0x3, - 0x3F, 0x0F, 0x0F, 0x0F, - 0x3, 0x3E, 0x1F, 0 -}; - -static const uint16 num_steps_table[] = { - 1, 2, 4, 5, - 6, 7, 8, 9, - 10, 12, 14, 16, - 18, 21, 24, 30, - 36, 50, 64, 82, - 100, 136, 160, 192, - 240, 276, 340, 460, - 600, 860, 1200, 1600 -}; - -static const byte note_to_f_num[] = { - 90, 91, 92, 92, 93, 94, 94, 95, - 96, 96, 97, 98, 98, 99, 100, 101, - 101, 102, 103, 104, 104, 105, 106, 107, - 107, 108, 109, 110, 111, 111, 112, 113, - 114, 115, 115, 116, 117, 118, 119, 120, - 121, 121, 122, 123, 124, 125, 126, 127, - 128, 129, 130, 131, 132, 132, 133, 134, - 135, 136, 137, 138, 139, 140, 141, 142, - 143, 145, 146, 147, 148, 149, 150, 151, - 152, 153, 154, 155, 157, 158, 159, 160, - 161, 162, 163, 165, 166, 167, 168, 169, - 171, 172, 173, 174, 176, 177, 178, 180, - 181, 182, 184, 185, 186, 188, 189, 190, - 192, 193, 194, 196, 197, 199, 200, 202, - 203, 205, 206, 208, 209, 211, 212, 214, - 215, 217, 218, 220, 222, 223, 225, 226, - 228, 230, 231, 233, 235, 236, 238, 240, - 242, 243, 245, 247, 249, 251, 252, 254 -}; - -static const byte map_gm_to_fm[128][30] = { - // 0x00 -{ 0xC2, 0xC5, 0x2B, 0x99, 0x58, 0xC2, 0x1F, 0x1E, 0xC8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x23 }, -{ 0x22, 0x53, 0x0E, 0x8A, 0x30, 0x14, 0x06, 0x1D, 0x7A, 0x5C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x06, 0x00, 0x1C, 0x79, 0x40, 0x02, 0x00, 0x4B, 0x79, 0x58, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xC2, 0x89, 0x2A, 0x89, 0x49, 0xC2, 0x16, 0x1C, 0xB8, 0x7C, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x23 }, -{ 0xC2, 0x17, 0x3D, 0x6A, 0x00, 0xC4, 0x2E, 0x2D, 0xC9, 0x20, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x06, 0x1E, 0x1C, 0x99, 0x00, 0x02, 0x3A, 0x4C, 0x79, 0x00, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x84, 0x40, 0x3B, 0x5A, 0x6F, 0x81, 0x0E, 0x3B, 0x5A, 0x7F, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x84, 0x40, 0x3B, 0x5A, 0x63, 0x81, 0x00, 0x3B, 0x5A, 0x7F, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x8C, 0x80, 0x05, 0xEA, 0x59, 0x82, 0x0A, 0x3C, 0xAA, 0x64, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x85, 0x40, 0x0D, 0xEC, 0x71, 0x84, 0x58, 0x3E, 0xCB, 0x7C, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x8A, 0xC0, 0x0C, 0xDC, 0x50, 0x88, 0x58, 0x3D, 0xDA, 0x7C, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xC9, 0x40, 0x2B, 0x78, 0x42, 0xC2, 0x04, 0x4C, 0x8A, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x1A }, -{ 0x2A, 0x0E, 0x17, 0x89, 0x28, 0x22, 0x0C, 0x1B, 0x09, 0x70, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE7, 0x9B, 0x08, 0x08, 0x26, 0xE2, 0x06, 0x0A, 0x08, 0x70, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xC5, 0x05, 0x00, 0xFC, 0x40, 0x84, 0x00, 0x00, 0xDC, 0x50, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x86, 0x40, 0x5D, 0x5A, 0x41, 0x81, 0x00, 0x0B, 0x5A, 0x7F, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, - // 0x10 -{ 0xED, 0x00, 0x7B, 0xC8, 0x40, 0xE1, 0x99, 0x4A, 0xE9, 0x7E, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE8, 0x4F, 0x3A, 0xD7, 0x7C, 0xE2, 0x97, 0x49, 0xF9, 0x7D, 0x05, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE1, 0x10, 0x2F, 0xF7, 0x7D, 0xF3, 0x45, 0x8F, 0xC7, 0x62, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x01, 0x8C, 0x9F, 0xDA, 0x70, 0xE4, 0x50, 0x9F, 0xDA, 0x6A, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x08, 0xD5, 0x9D, 0xA5, 0x45, 0xE2, 0x3F, 0x9F, 0xD6, 0x49, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE5, 0x0F, 0x7D, 0xB8, 0x2E, 0xA2, 0x0F, 0x7C, 0xC7, 0x61, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xF2, 0x2A, 0x9F, 0xDB, 0x01, 0xE1, 0x04, 0x8F, 0xD7, 0x62, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE4, 0x88, 0x9C, 0x50, 0x64, 0xE2, 0x18, 0x70, 0xC4, 0x7C, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x02, 0xA3, 0x0D, 0xDA, 0x01, 0xC2, 0x35, 0x5D, 0x58, 0x00, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x18 }, -{ 0x42, 0x55, 0x3E, 0xEB, 0x24, 0xD4, 0x08, 0x0D, 0xA9, 0x71, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x18 }, -{ 0xC2, 0x00, 0x2B, 0x17, 0x51, 0xC2, 0x1E, 0x4D, 0x97, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x19 }, -{ 0xC6, 0x01, 0x2D, 0xA7, 0x44, 0xC2, 0x06, 0x0E, 0xA7, 0x79, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xC2, 0x0C, 0x06, 0x06, 0x55, 0xC2, 0x3F, 0x09, 0x86, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0A }, -{ 0xC2, 0x2E, 0x4F, 0x77, 0x00, 0xC4, 0x08, 0x0E, 0x98, 0x59, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xC2, 0x30, 0x4F, 0xCA, 0x01, 0xC4, 0x0D, 0x0E, 0xB8, 0x7F, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xC4, 0x29, 0x4F, 0xCA, 0x03, 0xC8, 0x0D, 0x0C, 0xB7, 0x7D, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0B }, - // 0x20 -{ 0xC2, 0x40, 0x3C, 0x96, 0x58, 0xC4, 0xDE, 0x0E, 0xC7, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x20 }, -{ 0x31, 0x13, 0x2D, 0xD7, 0x3C, 0xE2, 0x18, 0x2E, 0xB8, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x22, 0x86, 0x0D, 0xD7, 0x50, 0xE4, 0x18, 0x5E, 0xB8, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x28 }, -{ 0xF2, 0x0A, 0x0D, 0xD7, 0x40, 0xE4, 0x1F, 0x5E, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xF2, 0x09, 0x4B, 0xD6, 0x48, 0xE4, 0x1F, 0x1C, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x28 }, -{ 0x62, 0x11, 0x0C, 0xE6, 0x3C, 0xE4, 0x1F, 0x0C, 0xC8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE2, 0x12, 0x3D, 0xE6, 0x34, 0xE4, 0x1F, 0x7D, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE2, 0x13, 0x3D, 0xE6, 0x34, 0xE4, 0x1F, 0x5D, 0xB8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xA2, 0x40, 0x5D, 0xBA, 0x3F, 0xE2, 0x00, 0x8F, 0xD8, 0x79, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE2, 0x40, 0x3D, 0xDA, 0x3B, 0xE1, 0x00, 0x7E, 0xD8, 0x7A, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x62, 0x00, 0x6D, 0xFA, 0x5D, 0xE2, 0x00, 0x8F, 0xC8, 0x79, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE1, 0x00, 0x4E, 0xDB, 0x4A, 0xE3, 0x18, 0x6F, 0xE9, 0x7E, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE1, 0x00, 0x4E, 0xDB, 0x66, 0xE2, 0x00, 0x7F, 0xE9, 0x7E, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x02, 0x0F, 0x66, 0xAA, 0x51, 0x02, 0x64, 0x29, 0xF9, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, -{ 0x16, 0x4A, 0x04, 0xBA, 0x39, 0xC2, 0x58, 0x2D, 0xCA, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, -{ 0x02, 0x00, 0x01, 0x7A, 0x79, 0x02, 0x3F, 0x28, 0xEA, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, - // 0x30 -{ 0x62, 0x53, 0x9C, 0xBA, 0x31, 0x62, 0x5B, 0xAD, 0xC9, 0x55, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xF2, 0x40, 0x6E, 0xDA, 0x49, 0xE2, 0x13, 0x8F, 0xF9, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE2, 0x40, 0x8F, 0xFA, 0x50, 0xF2, 0x04, 0x7F, 0xFA, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE4, 0xA0, 0xCE, 0x5B, 0x02, 0xE2, 0x32, 0x7F, 0xFB, 0x3D, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE6, 0x80, 0x9C, 0x99, 0x42, 0xE2, 0x04, 0x7D, 0x78, 0x60, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xEA, 0xA0, 0xAC, 0x67, 0x02, 0xE2, 0x00, 0x7C, 0x7A, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE7, 0x94, 0xAD, 0xB7, 0x03, 0xE2, 0x00, 0x7C, 0xBA, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xC3, 0x3F, 0x4B, 0xE9, 0x7E, 0xC1, 0x3F, 0x9B, 0xF9, 0x7F, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 }, -{ 0xB2, 0x20, 0xAD, 0xE9, 0x00, 0x62, 0x05, 0x8F, 0xC8, 0x68, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xF2, 0x00, 0x8F, 0xFB, 0x50, 0xF6, 0x47, 0x8F, 0xE9, 0x68, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xF2, 0x00, 0xAF, 0x88, 0x58, 0xF2, 0x54, 0x6E, 0xC9, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xF2, 0x2A, 0x9F, 0x98, 0x01, 0xE2, 0x84, 0x4E, 0x78, 0x6C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE2, 0x02, 0x9F, 0xB8, 0x48, 0x22, 0x89, 0x9F, 0xE8, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE2, 0x2A, 0x7F, 0xB8, 0x01, 0xE4, 0x00, 0x0D, 0xC5, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE4, 0x28, 0x8E, 0xE8, 0x01, 0xF2, 0x00, 0x4D, 0xD6, 0x7D, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x62, 0x23, 0x8F, 0xEA, 0x00, 0xF2, 0x00, 0x5E, 0xD9, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, - // 0x40 -{ 0xB4, 0x26, 0x6E, 0x98, 0x01, 0x62, 0x00, 0x7D, 0xC8, 0x7D, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE2, 0x2E, 0x20, 0xD9, 0x01, 0xF2, 0x0F, 0x90, 0xF8, 0x78, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE4, 0x28, 0x7E, 0xF8, 0x01, 0xE2, 0x23, 0x8E, 0xE8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xB8, 0x28, 0x9E, 0x98, 0x01, 0x62, 0x00, 0x3D, 0xC8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x62, 0x00, 0x8E, 0xC9, 0x3D, 0xE6, 0x00, 0x7E, 0xD8, 0x68, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE2, 0x00, 0x5F, 0xF9, 0x48, 0xE6, 0x98, 0x8F, 0xF8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x62, 0x0C, 0x6E, 0xD8, 0x3D, 0x2A, 0x06, 0x7D, 0xD8, 0x58, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE4, 0x00, 0x7E, 0x89, 0x38, 0xE6, 0x84, 0x80, 0xF8, 0x68, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE4, 0x80, 0x6C, 0xD9, 0x30, 0xE2, 0x00, 0x8D, 0xC8, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE2, 0x80, 0x88, 0x48, 0x40, 0xE2, 0x0A, 0x7D, 0xA8, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE4, 0x00, 0x77, 0xC5, 0x54, 0xE2, 0x00, 0x9E, 0xD7, 0x70, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE4, 0x80, 0x86, 0xB9, 0x64, 0xE2, 0x05, 0x9F, 0xD7, 0x78, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE2, 0x00, 0x68, 0x68, 0x56, 0xE2, 0x08, 0x9B, 0xB3, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE4, 0x00, 0xA6, 0x87, 0x41, 0xE2, 0x0A, 0x7E, 0xC9, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE4, 0x80, 0x9A, 0xB8, 0x48, 0xE2, 0x00, 0x9E, 0xF9, 0x60, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE2, 0x80, 0x8E, 0x64, 0x68, 0xE2, 0x28, 0x6F, 0x73, 0x7C, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, - // 0x50 -{ 0xE8, 0x00, 0x7D, 0x99, 0x54, 0xE6, 0x80, 0x80, 0xF8, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE6, 0x00, 0x9F, 0xB9, 0x6D, 0xE1, 0x00, 0x8F, 0xC8, 0x7D, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE4, 0x00, 0x09, 0x68, 0x4A, 0xE2, 0x2B, 0x9E, 0xF3, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xC4, 0x00, 0x99, 0xE8, 0x3B, 0xE2, 0x25, 0x6F, 0x93, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE6, 0x00, 0x6F, 0xDA, 0x69, 0xE2, 0x05, 0x2F, 0xD8, 0x6A, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xEC, 0x60, 0x9D, 0xC7, 0x00, 0xE2, 0x21, 0x7F, 0xC9, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE3, 0x00, 0x0F, 0xF7, 0x7D, 0xE1, 0x3F, 0x0F, 0xA7, 0x01, 0x0D, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE4, 0xA9, 0x0F, 0xA8, 0x02, 0xE2, 0x3C, 0x5F, 0xDA, 0x3C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE8, 0x40, 0x0D, 0x89, 0x7D, 0xE2, 0x17, 0x7E, 0xD9, 0x7C, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE1, 0x00, 0xDF, 0x8A, 0x56, 0xE2, 0x5E, 0xCF, 0xBA, 0x7E, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE2, 0x00, 0x0B, 0x68, 0x60, 0xE2, 0x01, 0x9E, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xEA, 0x00, 0xAE, 0xAB, 0x49, 0xE2, 0x00, 0xAE, 0xBA, 0x6C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xEB, 0x80, 0x8C, 0xCB, 0x3A, 0xE2, 0x86, 0xAF, 0xCA, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE5, 0x40, 0xDB, 0x3B, 0x3C, 0xE2, 0x80, 0xBE, 0xCA, 0x71, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE4, 0x00, 0x9E, 0xAA, 0x3D, 0xE1, 0x43, 0x0F, 0xBA, 0x7E, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE7, 0x40, 0xEC, 0xCA, 0x44, 0xE2, 0x03, 0xBF, 0xBA, 0x66, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, - // 0x60 -{ 0xEA, 0x00, 0x68, 0xB8, 0x48, 0xE2, 0x0A, 0x8E, 0xB8, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x61, 0x00, 0xBE, 0x99, 0x7E, 0xE3, 0x40, 0xCF, 0xCA, 0x7D, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xCD, 0x00, 0x0B, 0x00, 0x48, 0xC2, 0x58, 0x0C, 0x00, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x1C }, -{ 0xE2, 0x00, 0x0E, 0x00, 0x52, 0xE2, 0x58, 0x5F, 0xD0, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xCC, 0x00, 0x7D, 0xDA, 0x40, 0xC2, 0x00, 0x5E, 0x9B, 0x58, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE9, 0xC0, 0xEE, 0xD8, 0x43, 0xE2, 0x05, 0xDD, 0xAA, 0x70, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xDA, 0x00, 0x8F, 0xAC, 0x4A, 0x22, 0x05, 0x8D, 0x8A, 0x75, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x62, 0x8A, 0xCB, 0x7A, 0x74, 0xE6, 0x56, 0xAF, 0xDB, 0x70, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xC2, 0x41, 0xAC, 0x5B, 0x5B, 0xC2, 0x80, 0x0D, 0xCB, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x12 }, -{ 0x75, 0x00, 0x0E, 0xCB, 0x5A, 0xE2, 0x1E, 0x0A, 0xC9, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x10 }, -{ 0x41, 0x00, 0x0E, 0xEA, 0x53, 0xC2, 0x00, 0x08, 0xCA, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 }, -{ 0xC1, 0x40, 0x0C, 0x59, 0x6A, 0xC2, 0x80, 0x3C, 0xAB, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0D }, -{ 0x4B, 0x00, 0x0A, 0xF5, 0x61, 0xC2, 0x19, 0x0C, 0xE9, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 }, -{ 0x62, 0x00, 0x7F, 0xD8, 0x54, 0xEA, 0x00, 0x8F, 0xD8, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE1, 0x00, 0x7F, 0xD9, 0x56, 0xE1, 0x00, 0x8F, 0xD8, 0x7E, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0xE1, 0x00, 0x7F, 0xD9, 0x56, 0xE1, 0x00, 0x8F, 0xD8, 0x7E, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, - // 0x70 -{ 0xCF, 0x40, 0x09, 0xEA, 0x54, 0xC4, 0x00, 0x0C, 0xDB, 0x64, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0xCF, 0x40, 0x0C, 0xAA, 0x54, 0xC4, 0x00, 0x18, 0xF9, 0x64, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0xC9, 0x0E, 0x88, 0xD9, 0x3E, 0xC2, 0x08, 0x1A, 0xEA, 0x6C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, -{ 0x03, 0x00, 0x15, 0x00, 0x64, 0x02, 0x00, 0x08, 0x00, 0x7C, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0x01, 0x00, 0x47, 0xD7, 0x6C, 0x01, 0x3F, 0x0C, 0xFB, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, -{ 0x00, 0x00, 0x36, 0x67, 0x7C, 0x01, 0x3F, 0x0E, 0xFA, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, -{ 0x02, 0x00, 0x36, 0x68, 0x7C, 0x01, 0x3F, 0x0E, 0xFA, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, -{ 0xCB, 0x00, 0xAF, 0x00, 0x7E, 0xC0, 0x00, 0xC0, 0x06, 0x7F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0F }, -{ 0x05, 0x0D, 0x80, 0xA6, 0x7F, 0x0B, 0x38, 0xA9, 0xD8, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, -{ 0x0F, 0x00, 0x90, 0xFA, 0x68, 0x06, 0x00, 0xA7, 0x39, 0x54, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 }, -{ 0xC9, 0x15, 0xDD, 0xFF, 0x7C, 0x00, 0x00, 0xE7, 0xFC, 0x6C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x38 }, -{ 0x48, 0x3C, 0x30, 0xF6, 0x03, 0x0A, 0x38, 0x97, 0xE8, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, -{ 0x07, 0x80, 0x0B, 0xC8, 0x65, 0x02, 0x3F, 0x0C, 0xEA, 0x7C, 0x0F, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, -{ 0x00, 0x21, 0x66, 0x40, 0x03, 0x00, 0x3F, 0x47, 0x00, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0x08, 0x00, 0x0B, 0x3C, 0x7C, 0x08, 0x3F, 0x06, 0xF3, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0x00, 0x3F, 0x4C, 0xFB, 0x00, 0x00, 0x3F, 0x0A, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 } -}; - -static byte gm_percussion_to_fm[39][30] = { -{ 0x1A, 0x3F, 0x15, 0x05, 0x7C, 0x02, 0x21, 0x2B, 0xE4, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 }, -{ 0x11, 0x12, 0x04, 0x07, 0x7C, 0x02, 0x23, 0x0B, 0xE5, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, -{ 0x0A, 0x3F, 0x0B, 0x01, 0x7C, 0x1F, 0x1C, 0x46, 0xD0, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x01 }, -{ 0x00, 0x3F, 0x0F, 0x00, 0x7C, 0x10, 0x12, 0x07, 0x00, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0x0F, 0x3F, 0x0B, 0x00, 0x7C, 0x1F, 0x0F, 0x19, 0xD0, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0x00, 0x3F, 0x1F, 0x00, 0x7E, 0x1F, 0x16, 0x07, 0x00, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, -{ 0x12, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x1F, 0x4A, 0xD9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, -{ 0xCF, 0x7F, 0x08, 0xFF, 0x7E, 0x00, 0xC7, 0x2D, 0xF7, 0x73, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0x12, 0x3F, 0x05, 0x06, 0x7C, 0x43, 0x21, 0x0C, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, -{ 0xCF, 0x7F, 0x08, 0xCF, 0x7E, 0x00, 0x45, 0x2A, 0xF8, 0x4B, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0C }, -{ 0x12, 0x3F, 0x06, 0x17, 0x7C, 0x03, 0x27, 0x0B, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, -{ 0xCF, 0x7F, 0x08, 0xCD, 0x7E, 0x00, 0x40, 0x1A, 0x69, 0x63, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0C }, -{ 0x13, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x17, 0x0A, 0xD9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, -{ 0x15, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x21, 0x0C, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, -{ 0xCF, 0x3F, 0x2B, 0xFB, 0x7E, 0xC0, 0x1E, 0x1A, 0xCA, 0x7F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x10 }, -{ 0x17, 0x3F, 0x04, 0x09, 0x7C, 0x03, 0x22, 0x0D, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, -{ 0xCF, 0x3F, 0x0F, 0x5E, 0x7C, 0xC6, 0x13, 0x00, 0xCA, 0x7F, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, -{ 0xCF, 0x3F, 0x7E, 0x9D, 0x7C, 0xC8, 0xC0, 0x0A, 0xBA, 0x74, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 }, -{ 0xCF, 0x3F, 0x4D, 0x9F, 0x7C, 0xC6, 0x00, 0x08, 0xDA, 0x5B, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, -{ 0xCF, 0x3F, 0x5D, 0xAA, 0x7A, 0xC0, 0xA4, 0x67, 0x99, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0xCF, 0x3F, 0x4A, 0xFD, 0x7C, 0xCF, 0x00, 0x59, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0x0F, 0x18, 0x0A, 0xFA, 0x57, 0x06, 0x07, 0x06, 0x39, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0xCF, 0x3F, 0x2B, 0xFC, 0x7C, 0xCC, 0xC6, 0x0B, 0xEA, 0x7F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x10 }, -{ 0x05, 0x1A, 0x04, 0x00, 0x7C, 0x12, 0x10, 0x0C, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 }, -{ 0x04, 0x19, 0x04, 0x00, 0x7C, 0x12, 0x10, 0x2C, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, -{ 0x04, 0x0A, 0x04, 0x00, 0x6C, 0x01, 0x07, 0x0D, 0xFA, 0x74, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 }, -{ 0x15, 0x14, 0x05, 0x00, 0x7D, 0x01, 0x07, 0x5C, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, -{ 0x10, 0x10, 0x05, 0x08, 0x7C, 0x01, 0x08, 0x0D, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, -{ 0x11, 0x00, 0x06, 0x87, 0x7F, 0x02, 0x40, 0x09, 0x59, 0x68, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x08 }, -{ 0x13, 0x26, 0x04, 0x6A, 0x7F, 0x01, 0x00, 0x08, 0x5A, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x08 }, -{ 0xCF, 0x4E, 0x0C, 0xAA, 0x50, 0xC4, 0x00, 0x18, 0xF9, 0x54, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0xCF, 0x4E, 0x0C, 0xAA, 0x50, 0xC3, 0x00, 0x18, 0xF8, 0x54, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0xCB, 0x3F, 0x8F, 0x00, 0x7E, 0xC5, 0x00, 0x98, 0xD6, 0x5F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0D }, -{ 0x0C, 0x18, 0x87, 0xB3, 0x7F, 0x19, 0x10, 0x55, 0x75, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0x05, 0x11, 0x15, 0x00, 0x64, 0x02, 0x08, 0x08, 0x00, 0x5C, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0x04, 0x08, 0x15, 0x00, 0x48, 0x01, 0x08, 0x08, 0x00, 0x60, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, -{ 0xDA, 0x00, 0x53, 0x30, 0x68, 0x07, 0x1E, 0x49, 0xC4, 0x7E, 0x03, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, -{ 0x1C, 0x00, 0x07, 0xBC, 0x6C, 0x0C, 0x14, 0x0B, 0x6A, 0x7E, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, -{ 0x0A, 0x0E, 0x7F, 0x00, 0x7D, 0x13, 0x20, 0x28, 0x03, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 } -}; - -static const byte gm_percussion_lookup[128] = { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, - 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0xFF, 0xFF, 0x17, 0x18, 0x19, 0x1A, - 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x21, 0x22, 0x23, 0xFF, 0xFF, - 0x24, 0x25, 0xFF, 0xFF, 0xFF, 0x26, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF -}; - -static byte lookup_table[64][32]; - -static const byte volume_table[] = { - 0, 4, 7, 11, - 13, 16, 18, 20, - 22, 24, 26, 27, - 29, 30, 31, 33, - 34, 35, 36, 37, - 38, 39, 40, 41, - 42, 43, 44, 44, - 45, 46, 47, 47, - 48, 49, 49, 50, - 51, 51, 52, 53, - 53, 54, 54, 55, - 55, 56, 56, 57, - 57, 58, 58, 59, - 59, 60, 60, 60, - 61, 61, 62, 62, - 62, 63, 63, 63 -}; - -static int lookup_volume(int a, int b) { - if (b == 0) - return 0; - - if (b == 31) - return a; - - if (a < -63 || a > 63) { - return b * (a + 1) >> 5; - } - - if (b < 0) { - if (a < 0) { - return lookup_table[-a][-b]; - } else { - return -lookup_table[a][-b]; - } - } else { - if (a < 0) { - return -lookup_table[-a][b]; - } else { - return lookup_table[a][b]; - } - } -} - -static void create_lookup_table() { - int i, j; - int sum; - - for (i = 0; i < 64; i++) { - sum = i; - for (j = 0; j < 32; j++) { - lookup_table[i][j] = sum >> 5; - sum += i; - } - } - for (i = 0; i < 64; i++) - lookup_table[i][0] = 0; -} - -//////////////////////////////////////// -// -// AdLib MIDI driver -// -//////////////////////////////////////// - -class MidiDriver_ADLIB : public MidiDriver_Emulated { - friend class AdLibPart; - friend class AdLibPercussionChannel; - -public: - MidiDriver_ADLIB(Audio::Mixer *mixer); - - int open(); - void close(); - void send(uint32 b); - void send(byte channel, uint32 b); // Supports higher than channel 15 - uint32 property(int prop, uint32 param); - - void setPitchBendRange(byte channel, uint range); - void sysEx_customInstrument(byte channel, uint32 type, const byte *instr); - - MidiChannel *allocateChannel(); - MidiChannel *getPercussionChannel() { return &_percussion; } // Percussion partially supported - - - // AudioStream API - bool isStereo() const { return false; } - int getRate() const { return _mixer->getOutputRate(); } - -private: - bool _scummSmallHeader; // FIXME: This flag controls a special mode for SCUMM V3 games - - FM_OPL *_opl; - byte *_adlib_reg_cache; - - int _adlib_timer_counter; - - uint16 channel_table_2[9]; - int _voice_index; - int _timer_p; - int _timer_q; - uint16 curnote_table[9]; - AdLibVoice _voices[9]; - AdLibPart _parts[32]; - AdLibPercussionChannel _percussion; - - void generateSamples(int16 *buf, int len); - void onTimer(); - void part_key_on(AdLibPart *part, AdLibInstrument *instr, byte note, byte velocity); - void part_key_off(AdLibPart *part, byte note); - - void adlib_key_off(int chan); - void adlib_note_on(int chan, byte note, int mod); - void adlib_note_on_ex(int chan, byte note, int mod); - int adlib_get_reg_value_param(int chan, byte data); - void adlib_setup_channel(int chan, AdLibInstrument * instr, byte vol_1, byte vol_2); - byte adlib_get_reg_value(byte reg) { - return _adlib_reg_cache[reg]; - } - void adlib_set_param(int channel, byte param, int value); - void adlib_key_onoff(int channel); - void adlib_write(byte reg, byte value); - void adlib_playnote(int channel, int note); - - AdLibVoice *allocate_voice(byte pri); - - void mc_off(AdLibVoice * voice); - - static void link_mc(AdLibPart *part, AdLibVoice *voice); - void mc_inc_stuff(AdLibVoice *voice, Struct10 * s10, Struct11 * s11); - void mc_init_stuff(AdLibVoice *voice, Struct10 * s10, Struct11 * s11, byte flags, - InstrumentExtra * ie); - - void struct10_init(Struct10 * s10, InstrumentExtra * ie); - static byte struct10_ontimer(Struct10 * s10, Struct11 * s11); - static void struct10_setup(Struct10 * s10); - static int random_nr(int a); - void mc_key_on(AdLibVoice *voice, AdLibInstrument *instr, byte note, byte velocity); -}; - -// MidiChannel method implementations - -void AdLibPart::init(MidiDriver_ADLIB *owner, byte channel) { - _owner = owner; - _channel = channel; - _pri_eff = 127; - programChange(0); -} - -MidiDriver *AdLibPart::device() { - return _owner; -} - -void AdLibPart::send(uint32 b) { - _owner->send(_channel, b); -} - -void AdLibPart::noteOff(byte note) { -#ifdef DEBUG_ADLIB - debug(6, "%10d: noteOff(%d)", tick, note); -#endif - _owner->part_key_off(this, note); -} - -void AdLibPart::noteOn(byte note, byte velocity) { -#ifdef DEBUG_ADLIB - debug(6, "%10d: noteOn(%d,%d)", tick, note, velocity); -#endif - _owner->part_key_on(this, &_part_instr, note, velocity); -} - -void AdLibPart::programChange(byte program) { - if (program > 127) - return; - - uint i; - uint count = 0; - for (i = 0; i < ARRAYSIZE(map_gm_to_fm[0]); ++i) - count += map_gm_to_fm[program][i]; - if (!count) - warning("No AdLib instrument defined for GM program %d", (int) program); - _program = program; - memcpy(&_part_instr, &map_gm_to_fm[program], sizeof(AdLibInstrument)); -} - -void AdLibPart::pitchBend(int16 bend) { - AdLibVoice *voice; - - _pitchbend = bend; - for (voice = _voice; voice; voice = voice->_next) { - _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff, - (_pitchbend * _pitchbend_factor >> 6) + _detune_eff); - } -} - -void AdLibPart::controlChange(byte control, byte value) { - switch (control) { - case 0: - case 32: - break; // Bank select. Not supported - case 1: modulationWheel(value); break; - case 7: volume(value); break; - case 10: break; // Pan position. Not supported. - case 16: pitchBendFactor(value); break; - case 17: detune(value); break; - case 18: priority(value); break; - case 64: sustain(value > 0); break; - case 91: break; // Effects level. Not supported. - case 93: break; // Chorus level. Not supported. - case 119: break; // Unknown, used in Simon the Sorcerer 2 - case 121: // reset all controllers - modulationWheel(0); - pitchBendFactor(0); - detune(0); - sustain(0); - break; - case 123: allNotesOff(); break; - default: - warning("AdLib: Unknown control change message %d (%d)", (int) control, (int)value); - } -} - -void AdLibPart::modulationWheel(byte value) { - AdLibVoice *voice; - - _modwheel = value; - for (voice = _voice; voice; voice = voice->_next) { - if (voice->_s10a.active && voice->_s11a.flag0x40) - voice->_s10a.modwheel = _modwheel >> 2; - if (voice->_s10b.active && voice->_s11b.flag0x40) - voice->_s10b.modwheel = _modwheel >> 2; - } -} - -void AdLibPart::volume(byte value) { - AdLibVoice *voice; - - _vol_eff = value; - for (voice = _voice; voice; voice = voice->_next) { - _owner->adlib_set_param(voice->_channel, 0, volume_table[lookup_table[voice->_vol_2][_vol_eff >> 2]]); - if (voice->_twochan) { - _owner->adlib_set_param(voice->_channel, 13, volume_table[lookup_table[voice->_vol_1][_vol_eff >> 2]]); - } - } -} - -void AdLibPart::pitchBendFactor(byte value) { - AdLibVoice *voice; - - _pitchbend_factor = value; - for (voice = _voice; voice; voice = voice->_next) { - _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff, - (_pitchbend * _pitchbend_factor >> 6) + _detune_eff); - } -} - -void AdLibPart::detune(byte value) { - AdLibVoice *voice; - - _detune_eff = value; - for (voice = _voice; voice; voice = voice->_next) { - _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff, - (_pitchbend * _pitchbend_factor >> 6) + _detune_eff); - } -} - -void AdLibPart::priority(byte value) { - _pri_eff = value; -} - -void AdLibPart::sustain(bool value) { - AdLibVoice *voice; - - _pedal = value; - if (!value) { - for (voice = _voice; voice; voice = voice->_next) { - if (voice->_waitforpedal) - _owner->mc_off(voice); - } - } -} - -void AdLibPart::allNotesOff() { - while (_voice) - _owner->mc_off(_voice); -} - -void AdLibPart::sysEx_customInstrument(uint32 type, const byte *instr) { - if (type == 'ADL ') { - AdLibInstrument *i = &_part_instr; - memcpy(i, instr, sizeof(AdLibInstrument)); - } -} - -// MidiChannel method implementations for percussion - -AdLibPercussionChannel::~AdLibPercussionChannel() { - for (int i = 0; i < ARRAYSIZE(_customInstruments); ++i) { - delete _customInstruments[i]; - } -} - -void AdLibPercussionChannel::init(MidiDriver_ADLIB *owner, byte channel) { - AdLibPart::init(owner, channel); - _pri_eff = 0; - _vol_eff = 127; - - // Initialize the custom instruments data - memset(_notes, 0, sizeof(_notes)); - memset(_customInstruments, 0, sizeof(_customInstruments)); -} - -void AdLibPercussionChannel::noteOff(byte note) { - // Jamieson630: Unless I run into a specific instrument that - // may require a key off, I'm going to ignore this message. - // The rationale is that a percussion instrument should - // fade out of its own accord, and the AdLib instrument - // definitions used should follow this rule. Since - // percussion voices are allocated at the lowest priority - // anyway, we know that "hanging" percussion sounds will - // not prevent later musical instruments (or even other - // percussion sounds) from playing. -/* - _owner->part_key_off(this, note); -*/ -} - -void AdLibPercussionChannel::noteOn(byte note, byte velocity) { - AdLibInstrument *inst = NULL; - - // The custom instruments have priority over the default mapping - inst = _customInstruments[note]; - if (inst) - note = _notes[note]; - - if (!inst) { - // Use the default GM to FM mapping as a fallback as a fallback - byte key = gm_percussion_lookup[note]; - if (key != 0xFF) - inst = (AdLibInstrument *)&gm_percussion_to_fm[key]; - } - - if (!inst) { - debug(2, "No instrument FM definition for GM percussion key %d", (int)note); - return; - } - - _owner->part_key_on(this, inst, note, velocity); -} - -void AdLibPercussionChannel::sysEx_customInstrument(uint32 type, const byte *instr) { - if (type == 'ADLP') { - byte note = instr[0]; - _notes[note] = instr[1]; - - // Allocate memory for the new instruments - if (!_customInstruments[note]) { - _customInstruments[note] = new AdLibInstrument; - } - - // Save the new instrument data - _customInstruments[note]->mod_characteristic = instr[2]; - _customInstruments[note]->mod_scalingOutputLevel = instr[3]; - _customInstruments[note]->mod_attackDecay = instr[4]; - _customInstruments[note]->mod_sustainRelease = instr[5]; - _customInstruments[note]->mod_waveformSelect = instr[6]; - _customInstruments[note]->car_characteristic = instr[7]; - _customInstruments[note]->car_scalingOutputLevel = instr[8]; - _customInstruments[note]->car_attackDecay = instr[9]; - _customInstruments[note]->car_sustainRelease = instr[10]; - _customInstruments[note]->car_waveformSelect = instr[11]; - _customInstruments[note]->feedback = instr[12]; - } -} - -// MidiDriver method implementations - -MidiDriver_ADLIB::MidiDriver_ADLIB(Audio::Mixer *mixer) - : MidiDriver_Emulated(mixer) { - uint i; - - _scummSmallHeader = false; - - _adlib_reg_cache = 0; - - _adlib_timer_counter = 0; - _voice_index = 0; - for (i = 0; i < ARRAYSIZE(curnote_table); ++i) { - curnote_table[i] = 0; - } - - for (i = 0; i < ARRAYSIZE(_parts); ++i) { - _parts[i].init(this, i + ((i >= 9) ? 1 : 0)); - } - _percussion.init(this, 9); - _timer_p = 0xD69; - _timer_q = 0x411B; -} - -int MidiDriver_ADLIB::open() { - if (_isOpen) - return MERR_ALREADY_OPEN; - - MidiDriver_Emulated::open(); - - int i; - AdLibVoice *voice; - - for (i = 0, voice = _voices; i != ARRAYSIZE(_voices); i++, voice++) { - voice->_channel = i; - voice->_s11a.s10 = &voice->_s10b; - voice->_s11b.s10 = &voice->_s10a; - } - - _adlib_reg_cache = (byte *)calloc(256, 1); - - _opl = makeAdLibOPL(getRate()); - - adlib_write(1, 0x20); - adlib_write(8, 0x40); - adlib_write(0xBD, 0x00); - create_lookup_table(); - - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); - - return 0; -} - -void MidiDriver_ADLIB::close() { - if (!_isOpen) - return; - _isOpen = false; - - _mixer->stopHandle(_mixerSoundHandle); - - uint i; - for (i = 0; i < ARRAYSIZE(_voices); ++i) { - if (_voices[i]._part) - mc_off(&_voices[i]); - } - - // Turn off the OPL emulation - OPLDestroy(_opl); -// YM3812Shutdown(); - - free(_adlib_reg_cache); -} - -void MidiDriver_ADLIB::send(uint32 b) { - send(b & 0xF, b & 0xFFFFFFF0); -} - -void MidiDriver_ADLIB::send(byte chan, uint32 b) { - //byte param3 = (byte) ((b >> 24) & 0xFF); - byte param2 = (byte) ((b >> 16) & 0xFF); - byte param1 = (byte) ((b >> 8) & 0xFF); - byte cmd = (byte) (b & 0xF0); - - AdLibPart *part; - if (chan == 9) - part = &_percussion; - else - part = &_parts[chan]; - - switch (cmd) { - case 0x80:// Note Off - part->noteOff(param1); - break; - case 0x90: // Note On - part->noteOn(param1, param2); - break; - case 0xA0: // Aftertouch - break; // Not supported. - case 0xB0: // Control Change - part->controlChange(param1, param2); - break; - case 0xC0: // Program Change - part->programChange(param1); - break; - case 0xD0: // Channel Pressure - break; // Not supported. - case 0xE0: // Pitch Bend - part->pitchBend((param1 | (param2 << 7)) - 0x2000); - break; - case 0xF0: // SysEx - // We should never get here! SysEx information has to be - // sent via high-level semantic methods. - warning("MidiDriver_ADLIB: Receiving SysEx command on a send() call"); - break; - - default: - warning("MidiDriver_ADLIB: Unknown send() command 0x%02X", cmd); - } -} - -uint32 MidiDriver_ADLIB::property(int prop, uint32 param) { - switch (prop) { - case PROP_OLD_ADLIB: // Older games used a different operator volume algorithm - _scummSmallHeader = (param > 0); - if (_scummSmallHeader) { - _timer_p = 473; - _timer_q = 1000; - } else { - _timer_p = 0xD69; - _timer_q = 0x411B; - } - return 1; - } - - return 0; -} - -void MidiDriver_ADLIB::setPitchBendRange(byte channel, uint range) { - AdLibVoice *voice; - AdLibPart *part = &_parts[channel]; - - part->_pitchbend_factor = range; - for (voice = part->_voice; voice; voice = voice->_next) { - adlib_note_on(voice->_channel, voice->_note + part->_transpose_eff, - (part->_pitchbend * part->_pitchbend_factor >> 6) + part->_detune_eff); - } -} - -void MidiDriver_ADLIB::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) { - _parts[channel].sysEx_customInstrument(type, instr); -} - -MidiChannel *MidiDriver_ADLIB::allocateChannel() { - AdLibPart *part; - uint i; - - for (i = 0; i < ARRAYSIZE(_parts); ++i) { - part = &_parts[i]; - if (!part->_allocated) { - part->allocate(); - return part; - } - } - return NULL; -} - -// All the code brought over from IMuseAdLib - -void MidiDriver_ADLIB::adlib_write(byte reg, byte value) { - if (_adlib_reg_cache[reg] == value) - return; -#ifdef DEBUG_ADLIB - debug(6, "%10d: adlib_write[%x] = %x", tick, reg, value); -#endif - _adlib_reg_cache[reg] = value; - - OPLWriteReg(_opl, reg, value); -} - -void MidiDriver_ADLIB::generateSamples(int16 *data, int len) { - memset(data, 0, sizeof(int16) * len); - YM3812UpdateOne(_opl, data, len); -} - -void MidiDriver_ADLIB::onTimer() { - AdLibVoice *voice; - int i; - - _adlib_timer_counter += _timer_p; - while (_adlib_timer_counter >= _timer_q) { - _adlib_timer_counter -= _timer_q; -#ifdef DEBUG_ADLIB - tick++; -#endif - voice = _voices; - for (i = 0; i != ARRAYSIZE(_voices); i++, voice++) { - if (!voice->_part) - continue; - if (voice->_duration && (voice->_duration -= 0x11) <= 0) { - mc_off(voice); - return; - } - if (voice->_s10a.active) { - mc_inc_stuff(voice, &voice->_s10a, &voice->_s11a); - } - if (voice->_s10b.active) { - mc_inc_stuff(voice, &voice->_s10b, &voice->_s11b); - } - } - } -} - -void MidiDriver_ADLIB::mc_off(AdLibVoice *voice) { - AdLibVoice *tmp; - - adlib_key_off(voice->_channel); - - tmp = voice->_prev; - - if (voice->_next) - voice->_next->_prev = tmp; - if (tmp) - tmp->_next = voice->_next; - else - voice->_part->_voice = voice->_next; - voice->_part = NULL; -} - -void MidiDriver_ADLIB::mc_inc_stuff(AdLibVoice *voice, Struct10 *s10, Struct11 *s11) { - byte code; - AdLibPart *part = voice->_part; - - code = struct10_ontimer(s10, s11); - - if (code & 1) { - switch (s11->param) { - case 0: - voice->_vol_2 = s10->start_value + s11->modify_val; - if (!_scummSmallHeader) { - adlib_set_param(voice->_channel, 0, - volume_table[lookup_table[voice->_vol_2] - [part->_vol_eff >> 2]]); - } else { - adlib_set_param(voice->_channel, 0, voice->_vol_2); - } - break; - case 13: - voice->_vol_1 = s10->start_value + s11->modify_val; - if (voice->_twochan && !_scummSmallHeader) { - adlib_set_param(voice->_channel, 13, - volume_table[lookup_table[voice->_vol_1] - [part->_vol_eff >> 2]]); - } else { - adlib_set_param(voice->_channel, 13, voice->_vol_1); - } - break; - case 30: - s11->s10->modwheel = (char)s11->modify_val; - break; - case 31: - s11->s10->unk3 = (char)s11->modify_val; - break; - default: - adlib_set_param(voice->_channel, s11->param, - s10->start_value + s11->modify_val); - break; - } - } - - if (code & 2 && s11->flag0x10) - adlib_key_onoff(voice->_channel); -} - -void MidiDriver_ADLIB::adlib_key_off(int chan){ - byte reg = chan + 0xB0; - adlib_write(reg, adlib_get_reg_value(reg) & ~0x20); -} - -byte MidiDriver_ADLIB::struct10_ontimer(Struct10 *s10, Struct11 *s11) { - byte result = 0; - int i; - - if (s10->count && (s10->count -= 17) <= 0) { - s10->active = 0; - return 0; - } - - i = s10->cur_val + s10->speed_hi; - s10->speed_lo_counter += s10->speed_lo; - if (s10->speed_lo_counter >= s10->speed_lo_max) { - s10->speed_lo_counter -= s10->speed_lo_max; - i += s10->direction; - } - if (s10->cur_val != i || s10->modwheel != s10->modwheel_last) { - s10->cur_val = i; - s10->modwheel_last = s10->modwheel; - i = lookup_volume(i, s10->modwheel_last); - if (i != s11->modify_val) { - s11->modify_val = i; - result = 1; - } - } - - if (!--s10->num_steps) { - s10->active++; - if (s10->active > 4) { - if (s10->loop) { - s10->active = 1; - result |= 2; - struct10_setup(s10); - } else { - s10->active = 0; - } - } else { - struct10_setup(s10); - } - } - - return result; -} - -void MidiDriver_ADLIB::adlib_set_param(int channel, byte param, int value) { - const AdLibSetParams *as; - byte reg; - - assert(channel >= 0 && channel < 9); - - if (param <= 12) { - reg = channel_mappings_2[channel]; - } else if (param <= 25) { - param -= 13; - reg = channel_mappings[channel]; - } else if (param <= 27) { - param -= 13; - reg = channel; - } else if (param == 28 || param == 29) { - if (param == 28) - value -= 15; - else - value -= 383; - value <<= 4; - channel_table_2[channel] = value; - adlib_playnote(channel, curnote_table[channel] + value); - return; - } else { - return; - } - - as = &adlib_setparam_table[param]; - if (as->d) - value = as->d - value; - reg += as->a; - adlib_write(reg, (adlib_get_reg_value(reg) & ~as->c) | (((byte)value) << as->b)); -} - -void MidiDriver_ADLIB::adlib_key_onoff(int channel) { - byte val; - byte reg = channel + 0xB0; - assert(channel >= 0 && channel < 9); - - val = adlib_get_reg_value(reg); - adlib_write(reg, val & ~0x20); - adlib_write(reg, val | 0x20); -} - -void MidiDriver_ADLIB::struct10_setup(Struct10 *s10) { - int b, c, d, e, f, g, h; - byte t; - - b = s10->unk3; - f = s10->active - 1; - - t = s10->table_a[f]; - e = num_steps_table[lookup_table[t & 0x7F][b]]; - if (t & 0x80) { - e = random_nr(e); - } - if (e == 0) - e++; - - s10->num_steps = s10->speed_lo_max = e; - - if (f != 2) { - c = s10->max_value; - g = s10->start_value; - t = s10->table_b[f]; - d = lookup_volume(c, (t & 0x7F) - 31); - if (t & 0x80) { - d = random_nr(d); - } - if (d + g > c) { - h = c - g; - } else { - h = d; - if (d + g < 0) - h = -g; - } - h -= s10->cur_val; - } else { - h = 0; - } - - s10->speed_hi = h / e; - if (h < 0) { - h = -h; - s10->direction = -1; - } else { - s10->direction = 1; - } - - s10->speed_lo = h % e; - s10->speed_lo_counter = 0; -} - -void MidiDriver_ADLIB::adlib_playnote(int channel, int note) { - byte old, oct, notex; - int note2; - int i; - - note2 = (note >> 7) - 4; - note2 = (note2 < 128) ? note2 : 0; - - oct = (note2 / 12); - if (oct > 7) - oct = 7 << 2; - else - oct <<= 2; - notex = note2 % 12 + 3; - - old = adlib_get_reg_value(channel + 0xB0); - if (old & 0x20) { - old &= ~0x20; - if (oct > old) { - if (notex < 6) { - notex += 12; - oct -= 4; - } - } else if (oct < old) { - if (notex > 11) { - notex -= 12; - oct += 4; - } - } - } - - i = (notex << 3) + ((note >> 4) & 0x7); - adlib_write(channel + 0xA0, note_to_f_num[i]); - adlib_write(channel + 0xB0, oct | 0x20); -} - -int MidiDriver_ADLIB::random_nr(int a) { - static byte _rand_seed = 1; - if (_rand_seed & 1) { - _rand_seed >>= 1; - _rand_seed ^= 0xB8; - } else { - _rand_seed >>= 1; - } - return _rand_seed * a >> 8; -} - -void MidiDriver_ADLIB::part_key_off(AdLibPart *part, byte note) { - AdLibVoice *voice; - - for (voice = part->_voice; voice; voice = voice->_next) { - if (voice->_note == note) { - if (part->_pedal) - voice->_waitforpedal = true; - else - mc_off(voice); - } - } -} - -void MidiDriver_ADLIB::part_key_on(AdLibPart *part, AdLibInstrument *instr, byte note, byte velocity) { - AdLibVoice *voice; - - voice = allocate_voice(part->_pri_eff); - if (!voice) - return; - - link_mc(part, voice); - mc_key_on(voice, instr, note, velocity); -} - -AdLibVoice *MidiDriver_ADLIB::allocate_voice(byte pri) { - AdLibVoice *ac, *best = NULL; - int i; - - for (i = 0; i < 9; i++) { - if (++_voice_index >= 9) - _voice_index = 0; - ac = &_voices[_voice_index]; - if (!ac->_part) - return ac; - if (!ac->_next) { - if (ac->_part->_pri_eff <= pri) { - pri = ac->_part->_pri_eff; - best = ac; - } - } - } - - /* SCUMM V3 games don't have note priorities, first comes wins. */ - if (_scummSmallHeader) - return NULL; - - if (best) - mc_off(best); - return best; -} - -void MidiDriver_ADLIB::link_mc(AdLibPart *part, AdLibVoice *voice) { - voice->_part = part; - voice->_next = (AdLibVoice *)part->_voice; - part->_voice = voice; - voice->_prev = NULL; - - if (voice->_next) - voice->_next->_prev = voice; -} - -void MidiDriver_ADLIB::mc_key_on(AdLibVoice *voice, AdLibInstrument *instr, byte note, byte velocity) { - AdLibPart *part = voice->_part; - int c; - byte vol_1, vol_2; - - voice->_twochan = instr->feedback & 1; - voice->_note = note; - voice->_waitforpedal = false; - voice->_duration = instr->duration; - if (voice->_duration != 0) - voice->_duration *= 63; - - if (!_scummSmallHeader) - vol_1 = (instr->mod_scalingOutputLevel & 0x3F) + lookup_table[velocity >> 1][instr->mod_waveformSelect >> 2]; - else - vol_1 = 0x3f - (instr->mod_scalingOutputLevel & 0x3F); - if (vol_1 > 0x3F) - vol_1 = 0x3F; - voice->_vol_1 = vol_1; - - if (!_scummSmallHeader) - vol_2 = (instr->car_scalingOutputLevel & 0x3F) + lookup_table[velocity >> 1][instr->car_waveformSelect >> 2]; - else - vol_2 = 0x3f - (instr->car_scalingOutputLevel & 0x3F); - if (vol_2 > 0x3F) - vol_2 = 0x3F; - voice->_vol_2 = vol_2; - - c = part->_vol_eff >> 2; - - if (!_scummSmallHeader) { - vol_2 = volume_table[lookup_table[vol_2][c]]; - if (voice->_twochan) - vol_1 = volume_table[lookup_table[vol_1][c]]; - } - - adlib_setup_channel(voice->_channel, instr, vol_1, vol_2); - adlib_note_on_ex(voice->_channel, part->_transpose_eff + note, part->_detune_eff + (part->_pitchbend * part->_pitchbend_factor >> 6)); - - if (instr->flags_a & 0x80) { - mc_init_stuff(voice, &voice->_s10a, &voice->_s11a, instr->flags_a, &instr->extra_a); - } else { - voice->_s10a.active = 0; - } - - if (instr->flags_b & 0x80) { - mc_init_stuff(voice, &voice->_s10b, &voice->_s11b, instr->flags_b, &instr->extra_b); - } else { - voice->_s10b.active = 0; - } -} - -void MidiDriver_ADLIB::adlib_setup_channel(int chan, AdLibInstrument *instr, byte vol_1, byte vol_2) { - byte channel; - - assert(chan >= 0 && chan < 9); - - channel = channel_mappings[chan]; - adlib_write(channel + 0x20, instr->mod_characteristic); - adlib_write(channel + 0x40, (instr->mod_scalingOutputLevel | 0x3F) - vol_1 ); - adlib_write(channel + 0x60, 0xff & (~instr->mod_attackDecay)); - adlib_write(channel + 0x80, 0xff & (~instr->mod_sustainRelease)); - adlib_write(channel + 0xE0, instr->mod_waveformSelect); - - channel = channel_mappings_2[chan]; - adlib_write(channel + 0x20, instr->car_characteristic); - adlib_write(channel + 0x40, (instr->car_scalingOutputLevel | 0x3F) - vol_2 ); - adlib_write(channel + 0x60, 0xff & (~instr->car_attackDecay)); - adlib_write(channel + 0x80, 0xff & (~instr->car_sustainRelease)); - adlib_write(channel + 0xE0, instr->car_waveformSelect); - - adlib_write((byte)chan + 0xC0, instr->feedback); -} - -void MidiDriver_ADLIB::adlib_note_on_ex(int chan, byte note, int mod) -{ - int code; - assert(chan >= 0 && chan < 9); - code = (note << 7) + mod; - curnote_table[chan] = code; - channel_table_2[chan] = 0; - adlib_playnote(chan, code); -} - -void MidiDriver_ADLIB::mc_init_stuff(AdLibVoice *voice, Struct10 * s10, - Struct11 * s11, byte flags, InstrumentExtra * ie) { - AdLibPart *part = voice->_part; - s11->modify_val = 0; - s11->flag0x40 = flags & 0x40; - s10->loop = flags & 0x20; - s11->flag0x10 = flags & 0x10; - s11->param = param_table_1[flags & 0xF]; - s10->max_value = maxval_table[flags & 0xF]; - s10->unk3 = 31; - if (s11->flag0x40) { - s10->modwheel = part->_modwheel >> 2; - } else { - s10->modwheel = 31; - } - - switch (s11->param) { - case 0: - s10->start_value = voice->_vol_2; - break; - case 13: - s10->start_value = voice->_vol_1; - break; - case 30: - s10->start_value = 31; - s11->s10->modwheel = 0; - break; - case 31: - s10->start_value = 0; - s11->s10->unk3 = 0; - break; - default: - s10->start_value = adlib_get_reg_value_param(voice->_channel, s11->param); - } - - struct10_init(s10, ie); -} - -void MidiDriver_ADLIB::struct10_init(Struct10 *s10, InstrumentExtra *ie) { - s10->active = 1; - if (!_scummSmallHeader) { - s10->cur_val = 0; - } else { - s10->cur_val = s10->start_value; - s10->start_value = 0; - } - s10->modwheel_last = 31; - s10->count = ie->a; - if (s10->count) - s10->count *= 63; - s10->table_a[0] = ie->b; - s10->table_a[1] = ie->d; - s10->table_a[2] = ie->f; - s10->table_a[3] = ie->g; - - s10->table_b[0] = ie->c; - s10->table_b[1] = ie->e; - s10->table_b[2] = 0; - s10->table_b[3] = ie->h; - - struct10_setup(s10); -} - -int MidiDriver_ADLIB::adlib_get_reg_value_param(int chan, byte param) { - const AdLibSetParams *as; - byte val; - byte channel; - - assert(chan >= 0 && chan < 9); - - if (param <= 12) { - channel = channel_mappings_2[chan]; - } else if (param <= 25) { - param -= 13; - channel = channel_mappings[chan]; - } else if (param <= 27) { - param -= 13; - channel = chan; - } else if (param == 28) { - return 0xF; - } else if (param == 29) { - return 0x17F; - } else { - return 0; - } - - as = &adlib_setparam_table[param]; - val = adlib_get_reg_value(channel + as->a); - val &= as->c; - val >>= as->b; - if (as->d) - val = as->d - val; - - return val; -} - -void MidiDriver_ADLIB::adlib_note_on(int chan, byte note, int mod) { - int code; - assert(chan >= 0 && chan < 9); - code = (note << 7) + mod; - curnote_table[chan] = code; - adlib_playnote(chan, (int16) channel_table_2[chan] + code); -} - - -// Plugin interface - -class AdLibEmuMusicPlugin : public MusicPluginObject { -public: - const char *getName() const { - return _s("AdLib Emulator"); - } - - const char *getId() const { - return "adlib"; - } - - MusicDevices getDevices() const; - Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const; -}; - -MusicDevices AdLibEmuMusicPlugin::getDevices() const { - MusicDevices devices; - devices.push_back(MusicDevice(this, "", MT_ADLIB)); - return devices; -} - -Common::Error AdLibEmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const { - *mididriver = new MidiDriver_ADLIB(g_system->getMixer()); - - return Common::kNoError; -} - -//#if PLUGIN_ENABLED_DYNAMIC(ADLIB) - //REGISTER_PLUGIN_DYNAMIC(ADLIB, PLUGIN_TYPE_MUSIC, AdLibEmuMusicPlugin); -//#else - REGISTER_PLUGIN_STATIC(ADLIB, PLUGIN_TYPE_MUSIC, AdLibEmuMusicPlugin); -//#endif diff --git a/sound/softsynth/appleiigs.cpp b/sound/softsynth/appleiigs.cpp deleted file mode 100644 index 9004d1f0ab..0000000000 --- a/sound/softsynth/appleiigs.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* ScummVM - Graphic Adventure Engine -* -* ScummVM is the legal property of its developers, whose names -* are too numerous to list here. Please refer to the COPYRIGHT -* file distributed with this source distribution. -* -* This program is free software; you can redistribute it and/or -* modify it under the terms of the GNU General Public License -* as published by the Free Software Foundation; either version 2 -* of the License, or (at your option) any later version. - -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. - -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -* -* $URL$ -* $Id$ -* -*/ - -#include "sound/null.h" - -// Plugin interface -// (This can only create a null driver since apple II gs support seeems not to be implemented -// and also is not part of the midi driver architecture. But we need the plugin for the options -// menu in the launcher and for MidiDriver::detectDevice() which is more or less used by all engines.) - -class AppleIIGSMusicPlugin : public NullMusicPlugin { -public: - const char *getName() const { - return _s("Apple II GS Emulator (NOT IMPLEMENTED)"); - } - - const char *getId() const { - return "appleIIgs"; - } - - MusicDevices getDevices() const; -}; - -MusicDevices AppleIIGSMusicPlugin::getDevices() const { - MusicDevices devices; - devices.push_back(MusicDevice(this, "", MT_APPLEIIGS)); - return devices; -} - -//#if PLUGIN_ENABLED_DYNAMIC(APPLEIIGS) - //REGISTER_PLUGIN_DYNAMIC(APPLEIIGS, PLUGIN_TYPE_MUSIC, AppleIIGSMusicPlugin); -//#else - REGISTER_PLUGIN_STATIC(APPLEIIGS, PLUGIN_TYPE_MUSIC, AppleIIGSMusicPlugin); -//#endif - diff --git a/sound/softsynth/cms.cpp b/sound/softsynth/cms.cpp deleted file mode 100644 index b307146f14..0000000000 --- a/sound/softsynth/cms.cpp +++ /dev/null @@ -1,376 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#include "sound/softsynth/cms.h" -#include "sound/null.h" - -#include "common/textconsole.h" -#include "common/translation.h" -#include "common/debug.h" - -// CMS/Gameblaster Emulation taken from DosBox - -#define LEFT 0x00 -#define RIGHT 0x01 - -static const byte envelope[8][64] = { - /* zero amplitude */ - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - /* maximum amplitude */ - {15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, - 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, - 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, - 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, }, - /* single decay */ - {15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - /* repetitive decay */ - {15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, - 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, - 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, - 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }, - /* single triangular */ - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, - 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - /* repetitive triangular */ - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, - 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, - 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }, - /* single attack */ - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - /* repetitive attack */ - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 } -}; - -static const int amplitude_lookup[16] = { - 0*32767/16, 1*32767/16, 2*32767/16, 3*32767/16, - 4*32767/16, 5*32767/16, 6*32767/16, 7*32767/16, - 8*32767/16, 9*32767/16, 10*32767/16, 11*32767/16, - 12*32767/16, 13*32767/16, 14*32767/16, 15*32767/16 -}; - -void CMSEmulator::portWrite(int port, int val) { - switch (port) { - case 0x220: - portWriteIntern(0, 1, val); - break; - - case 0x221: - _saa1099[0].selected_reg = val & 0x1f; - if (_saa1099[0].selected_reg == 0x18 || _saa1099[0].selected_reg == 0x19) { - /* clock the envelope channels */ - if (_saa1099[0].env_clock[0]) - envelope(0, 0); - if (_saa1099[0].env_clock[1]) - envelope(0, 1); - } - break; - - case 0x222: - portWriteIntern(1, 1, val); - break; - - case 0x223: - _saa1099[1].selected_reg = val & 0x1f; - if (_saa1099[1].selected_reg == 0x18 || _saa1099[1].selected_reg == 0x19) { - /* clock the envelope channels */ - if (_saa1099[1].env_clock[0]) - envelope(1, 0); - if (_saa1099[1].env_clock[1]) - envelope(1, 1); - } - break; - - default: - warning("CMSEmulator got port: 0x%X", port); - break; - } -} - -void CMSEmulator::readBuffer(int16 *buffer, const int numSamples) { - update(0, &buffer[0], numSamples); - update(1, &buffer[0], numSamples); -} - -void CMSEmulator::envelope(int chip, int ch) { - SAA1099 *saa = &_saa1099[chip]; - if (saa->env_enable[ch]) { - int step, mode, mask; - mode = saa->env_mode[ch]; - /* step from 0..63 and then loop in steps 32..63 */ - step = saa->env_step[ch] = ((saa->env_step[ch] + 1) & 0x3f) | (saa->env_step[ch] & 0x20); - - mask = 15; - if (saa->env_bits[ch]) - mask &= ~1; /* 3 bit resolution, mask LSB */ - - saa->channels[ch*3+0].envelope[ LEFT] = - saa->channels[ch*3+1].envelope[ LEFT] = - saa->channels[ch*3+2].envelope[ LEFT] = ::envelope[mode][step] & mask; - if (saa->env_reverse_right[ch] & 0x01) { - saa->channels[ch*3+0].envelope[RIGHT] = - saa->channels[ch*3+1].envelope[RIGHT] = - saa->channels[ch*3+2].envelope[RIGHT] = (15 - ::envelope[mode][step]) & mask; - } else { - saa->channels[ch*3+0].envelope[RIGHT] = - saa->channels[ch*3+1].envelope[RIGHT] = - saa->channels[ch*3+2].envelope[RIGHT] = ::envelope[mode][step] & mask; - } - } else { - /* envelope mode off, set all envelope factors to 16 */ - saa->channels[ch*3+0].envelope[ LEFT] = - saa->channels[ch*3+1].envelope[ LEFT] = - saa->channels[ch*3+2].envelope[ LEFT] = - saa->channels[ch*3+0].envelope[RIGHT] = - saa->channels[ch*3+1].envelope[RIGHT] = - saa->channels[ch*3+2].envelope[RIGHT] = 16; - } -} - -void CMSEmulator::update(int chip, int16 *buffer, int length) { - struct SAA1099 *saa = &_saa1099[chip]; - int j, ch; - - /* if the channels are disabled we're done */ - if (!saa->all_ch_enable) { - /* init output data */ - if (chip == 0) { - memset(buffer, 0, sizeof(int16)*length*2); - } - return; - } - - if (chip == 0) { - memset(buffer, 0, sizeof(int16)*length*2); - } - - for (ch = 0; ch < 2; ch++) { - switch (saa->noise_params[ch]) { - case 0: saa->noise[ch].freq = 31250.0 * 2; break; - case 1: saa->noise[ch].freq = 15625.0 * 2; break; - case 2: saa->noise[ch].freq = 7812.5 * 2; break; - case 3: saa->noise[ch].freq = saa->channels[ch * 3].freq; break; - } - } - - /* fill all data needed */ - for (j = 0; j < length; ++j) { - int output_l = 0, output_r = 0; - - /* for each channel */ - for (ch = 0; ch < 6; ch++) { - if (saa->channels[ch].freq == 0.0) - saa->channels[ch].freq = (double)((2 * 15625) << saa->channels[ch].octave) / - (511.0 - (double)saa->channels[ch].frequency); - - /* check the actual position in the square wave */ - saa->channels[ch].counter -= saa->channels[ch].freq; - while (saa->channels[ch].counter < 0) { - /* calculate new frequency now after the half wave is updated */ - saa->channels[ch].freq = (double)((2 * 15625) << saa->channels[ch].octave) / - (511.0 - (double)saa->channels[ch].frequency); - - saa->channels[ch].counter += _sampleRate; - saa->channels[ch].level ^= 1; - - /* eventually clock the envelope counters */ - if (ch == 1 && saa->env_clock[0] == 0) - envelope(chip, 0); - if (ch == 4 && saa->env_clock[1] == 0) - envelope(chip, 1); - } - - /* if the noise is enabled */ - if (saa->channels[ch].noise_enable) { - /* if the noise level is high (noise 0: chan 0-2, noise 1: chan 3-5) */ - if (saa->noise[ch/3].level & 1) { - /* subtract to avoid overflows, also use only half amplitude */ - output_l -= saa->channels[ch].amplitude[ LEFT] * saa->channels[ch].envelope[ LEFT] / 16 / 2; - output_r -= saa->channels[ch].amplitude[RIGHT] * saa->channels[ch].envelope[RIGHT] / 16 / 2; - } - } - - /* if the square wave is enabled */ - if (saa->channels[ch].freq_enable) { - /* if the channel level is high */ - if (saa->channels[ch].level & 1) { - output_l += saa->channels[ch].amplitude[ LEFT] * saa->channels[ch].envelope[ LEFT] / 16; - output_r += saa->channels[ch].amplitude[RIGHT] * saa->channels[ch].envelope[RIGHT] / 16; - } - } - } - - for (ch = 0; ch < 2; ch++) { - /* check the actual position in noise generator */ - saa->noise[ch].counter -= saa->noise[ch].freq; - while (saa->noise[ch].counter < 0) { - saa->noise[ch].counter += _sampleRate; - if (((saa->noise[ch].level & 0x4000) == 0) == ((saa->noise[ch].level & 0x0040) == 0) ) - saa->noise[ch].level = (saa->noise[ch].level << 1) | 1; - else - saa->noise[ch].level <<= 1; - } - } - /* write sound data to the buffer */ - buffer[j*2] += output_l / 6; - buffer[j*2+1] += output_r / 6; - } -} - -void CMSEmulator::portWriteIntern(int chip, int offset, int data) { - SAA1099 *saa = &_saa1099[chip]; - int reg = saa->selected_reg; - int ch; - - switch (reg) { - /* channel i amplitude */ - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - ch = reg & 7; - saa->channels[ch].amplitude[LEFT] = amplitude_lookup[data & 0x0f]; - saa->channels[ch].amplitude[RIGHT] = amplitude_lookup[(data >> 4) & 0x0f]; - break; - - /* channel i frequency */ - case 0x08: - case 0x09: - case 0x0a: - case 0x0b: - case 0x0c: - case 0x0d: - ch = reg & 7; - saa->channels[ch].frequency = data & 0xff; - break; - - /* channel i octave */ - case 0x10: - case 0x11: - case 0x12: - ch = (reg - 0x10) << 1; - saa->channels[ch + 0].octave = data & 0x07; - saa->channels[ch + 1].octave = (data >> 4) & 0x07; - break; - - /* channel i frequency enable */ - case 0x14: - saa->channels[0].freq_enable = data & 0x01; - saa->channels[1].freq_enable = data & 0x02; - saa->channels[2].freq_enable = data & 0x04; - saa->channels[3].freq_enable = data & 0x08; - saa->channels[4].freq_enable = data & 0x10; - saa->channels[5].freq_enable = data & 0x20; - break; - - /* channel i noise enable */ - case 0x15: - saa->channels[0].noise_enable = data & 0x01; - saa->channels[1].noise_enable = data & 0x02; - saa->channels[2].noise_enable = data & 0x04; - saa->channels[3].noise_enable = data & 0x08; - saa->channels[4].noise_enable = data & 0x10; - saa->channels[5].noise_enable = data & 0x20; - break; - - /* noise generators parameters */ - case 0x16: - saa->noise_params[0] = data & 0x03; - saa->noise_params[1] = (data >> 4) & 0x03; - break; - - /* envelope generators parameters */ - case 0x18: - case 0x19: - ch = reg - 0x18; - saa->env_reverse_right[ch] = data & 0x01; - saa->env_mode[ch] = (data >> 1) & 0x07; - saa->env_bits[ch] = data & 0x10; - saa->env_clock[ch] = data & 0x20; - saa->env_enable[ch] = data & 0x80; - /* reset the envelope */ - saa->env_step[ch] = 0; - break; - - /* channels enable & reset generators */ - case 0x1c: - saa->all_ch_enable = data & 0x01; - saa->sync_state = data & 0x02; - if (data & 0x02) { - int i; - /* Synch & Reset generators */ - for (i = 0; i < 6; i++) { - saa->channels[i].level = 0; - saa->channels[i].counter = 0.0; - } - } - break; - - default: - // The CMS allows all registers to be written, so we just output some debug - // message here - debug(5, "CMS Unknown write to reg %x with %x",reg, data); - } -} - -class CMSMusicPlugin : public NullMusicPlugin { -public: - const char *getName() const { - return _s("Creative Music System Emulator"); - } - - const char *getId() const { - return "cms"; - } - - MusicDevices getDevices() const; -}; - -MusicDevices CMSMusicPlugin::getDevices() const { - MusicDevices devices; - devices.push_back(MusicDevice(this, "", MT_CMS)); - return devices; -} - -//#if PLUGIN_ENABLED_DYNAMIC(CMS) - //REGISTER_PLUGIN_DYNAMIC(CMS, PLUGIN_TYPE_MUSIC, CMSMusicPlugin); -//#else - REGISTER_PLUGIN_STATIC(CMS, PLUGIN_TYPE_MUSIC, CMSMusicPlugin); -//#endif diff --git a/sound/softsynth/cms.h b/sound/softsynth/cms.h deleted file mode 100644 index d5bb7f0a42..0000000000 --- a/sound/softsynth/cms.h +++ /dev/null @@ -1,92 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#ifndef SOUND_SOFTSYNTH_CMS_H -#define SOUND_SOFTSYNTH_CMS_H - -#include "common/scummsys.h" - -/* this structure defines a channel */ -struct saa1099_channel { - int frequency; /* frequency (0x00..0xff) */ - int freq_enable; /* frequency enable */ - int noise_enable; /* noise enable */ - int octave; /* octave (0x00..0x07) */ - int amplitude[2]; /* amplitude (0x00..0x0f) */ - int envelope[2]; /* envelope (0x00..0x0f or 0x10 == off) */ - - /* vars to simulate the square wave */ - double counter; - double freq; - int level; -}; - -/* this structure defines a noise channel */ -struct saa1099_noise { - /* vars to simulate the noise generator output */ - double counter; - double freq; - int level; /* noise polynomal shifter */ -}; - -/* this structure defines a SAA1099 chip */ -struct SAA1099 { - int stream; /* our stream */ - int noise_params[2]; /* noise generators parameters */ - int env_enable[2]; /* envelope generators enable */ - int env_reverse_right[2]; /* envelope reversed for right channel */ - int env_mode[2]; /* envelope generators mode */ - int env_bits[2]; /* non zero = 3 bits resolution */ - int env_clock[2]; /* envelope clock mode (non-zero external) */ - int env_step[2]; /* current envelope step */ - int all_ch_enable; /* all channels enable */ - int sync_state; /* sync all channels */ - int selected_reg; /* selected register */ - struct saa1099_channel channels[6]; /* channels */ - struct saa1099_noise noise[2]; /* noise generators */ -}; - -class CMSEmulator { -public: - CMSEmulator(uint32 sampleRate) { - _sampleRate = sampleRate; - memset(_saa1099, 0, sizeof(SAA1099)*2); - } - - ~CMSEmulator() { } - - void portWrite(int port, int val); - void readBuffer(int16 *buffer, const int numSamples); -private: - uint32 _sampleRate; - - SAA1099 _saa1099[2]; - - void envelope(int chip, int ch); - void update(int chip, int16 *buffer, int length); - void portWriteIntern(int chip, int offset, int data); -}; - - -#endif diff --git a/sound/softsynth/emumidi.h b/sound/softsynth/emumidi.h deleted file mode 100644 index 3fb2733b71..0000000000 --- a/sound/softsynth/emumidi.h +++ /dev/null @@ -1,116 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#ifndef SOUND_SOFTSYNTH_EMUMIDI_H -#define SOUND_SOFTSYNTH_EMUMIDI_H - -#include "sound/audiostream.h" -#include "sound/mididrv.h" -#include "sound/mixer.h" - -#define FIXP_SHIFT 16 - -class MidiDriver_Emulated : public Audio::AudioStream, public MidiDriver { -protected: - bool _isOpen; - Audio::Mixer *_mixer; - Audio::SoundHandle _mixerSoundHandle; - -private: - Common::TimerManager::TimerProc _timerProc; - void *_timerParam; - - int _nextTick; - int _samplesPerTick; - -protected: - virtual void generateSamples(int16 *buf, int len) = 0; - virtual void onTimer() {} - - int _baseFreq; - -public: - MidiDriver_Emulated(Audio::Mixer *mixer) : _mixer(mixer) { - _isOpen = false; - - _timerProc = 0; - _timerParam = 0; - - _nextTick = 0; - _samplesPerTick = 0; - - _baseFreq = 250; - } - - int open() { - _isOpen = true; - - int d = getRate() / _baseFreq; - int r = getRate() % _baseFreq; - - // This is equivalent to (getRate() << FIXP_SHIFT) / BASE_FREQ - // but less prone to arithmetic overflow. - - _samplesPerTick = (d << FIXP_SHIFT) + (r << FIXP_SHIFT) / _baseFreq; - return 0; - } - - void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { - _timerProc = timer_proc; - _timerParam = timer_param; - } - - uint32 getBaseTempo() { return 1000000 / _baseFreq; } - - - // AudioStream API - int readBuffer(int16 *data, const int numSamples) { - const int stereoFactor = isStereo() ? 2 : 1; - int len = numSamples / stereoFactor; - int step; - - do { - step = len; - if (step > (_nextTick >> FIXP_SHIFT)) - step = (_nextTick >> FIXP_SHIFT); - - generateSamples(data, step); - - _nextTick -= step << FIXP_SHIFT; - if (!(_nextTick >> FIXP_SHIFT)) { - if (_timerProc) - (*_timerProc)(_timerParam); - onTimer(); - _nextTick += _samplesPerTick; - } - data += step * stereoFactor; - len -= step; - } while (len); - - return numSamples; - } - bool endOfData() const { return false; } -}; - -#endif diff --git a/sound/softsynth/fluidsynth.cpp b/sound/softsynth/fluidsynth.cpp deleted file mode 100644 index fcb4591a20..0000000000 --- a/sound/softsynth/fluidsynth.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#include "common/scummsys.h" - -#ifdef USE_FLUIDSYNTH - -#include "common/config-manager.h" -#include "sound/musicplugin.h" -#include "sound/mpu401.h" -#include "sound/softsynth/emumidi.h" - -#include - -class MidiDriver_FluidSynth : public MidiDriver_Emulated { -private: - MidiChannel_MPU401 _midiChannels[16]; - fluid_settings_t *_settings; - fluid_synth_t *_synth; - int _soundFont; - int _outputRate; - Audio::SoundHandle _handle; - -protected: - // Because GCC complains about casting from const to non-const... - void setInt(const char *name, int val); - void setNum(const char *name, double num); - void setStr(const char *name, const char *str); - - void generateSamples(int16 *buf, int len); - -public: - MidiDriver_FluidSynth(Audio::Mixer *mixer); - - int open(); - void close(); - void send(uint32 b); - - MidiChannel *allocateChannel(); - MidiChannel *getPercussionChannel(); - - // AudioStream API - bool isStereo() const { return true; } - int getRate() const { return _outputRate; } -}; - -// MidiDriver method implementations - -MidiDriver_FluidSynth::MidiDriver_FluidSynth(Audio::Mixer *mixer) - : MidiDriver_Emulated(mixer) { - - for (int i = 0; i < ARRAYSIZE(_midiChannels); i++) { - _midiChannels[i].init(this, i); - } - - // It ought to be possible to get FluidSynth to generate samples at - // lower - - _outputRate = _mixer->getOutputRate(); - if (_outputRate < 22050) - _outputRate = 22050; - else if (_outputRate > 96000) - _outputRate = 96000; -} - -void MidiDriver_FluidSynth::setInt(const char *name, int val) { - char *name2 = strdup(name); - - fluid_settings_setint(_settings, name2, val); - free(name2); -} - -void MidiDriver_FluidSynth::setNum(const char *name, double val) { - char *name2 = strdup(name); - - fluid_settings_setnum(_settings, name2, val); - free(name2); -} - -void MidiDriver_FluidSynth::setStr(const char *name, const char *val) { - char *name2 = strdup(name); - char *val2 = strdup(val); - - fluid_settings_setstr(_settings, name2, val2); - free(name2); - free(val2); -} - -int MidiDriver_FluidSynth::open() { - if (_isOpen) - return MERR_ALREADY_OPEN; - - if (!ConfMan.hasKey("soundfont")) - error("FluidSynth requires a 'soundfont' setting"); - - _settings = new_fluid_settings(); - - // The default gain setting is ridiculously low - at least for me. This - // cannot be fixed by ScummVM's volume settings because they can only - // soften the sound, not amplify it, so instead we add an option to - // adjust the gain of FluidSynth itself. - - double gain = (double)ConfMan.getInt("midi_gain") / 100.0; - - setNum("synth.gain", gain); - setNum("synth.sample-rate", _outputRate); - - _synth = new_fluid_synth(_settings); - - // In theory, this ought to reduce CPU load... but it doesn't make any - // noticeable difference for me, so disable it for now. - - // fluid_synth_set_interp_method(_synth, -1, FLUID_INTERP_LINEAR); - // fluid_synth_set_reverb_on(_synth, 0); - // fluid_synth_set_chorus_on(_synth, 0); - - const char *soundfont = ConfMan.get("soundfont").c_str(); - - _soundFont = fluid_synth_sfload(_synth, soundfont, 1); - if (_soundFont == -1) - error("Failed loading custom sound font '%s'", soundfont); - - MidiDriver_Emulated::open(); - - // The MT-32 emulator uses kSFXSoundType here. I don't know why. - _mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); - return 0; -} - -void MidiDriver_FluidSynth::close() { - if (!_isOpen) - return; - _isOpen = false; - - _mixer->stopHandle(_handle); - - if (_soundFont != -1) - fluid_synth_sfunload(_synth, _soundFont, 1); - - delete_fluid_synth(_synth); - delete_fluid_settings(_settings); -} - -void MidiDriver_FluidSynth::send(uint32 b) { - //byte param3 = (byte) ((b >> 24) & 0xFF); - uint param2 = (byte) ((b >> 16) & 0xFF); - uint param1 = (byte) ((b >> 8) & 0xFF); - byte cmd = (byte) (b & 0xF0); - byte chan = (byte) (b & 0x0F); - - switch (cmd) { - case 0x80: // Note Off - fluid_synth_noteoff(_synth, chan, param1); - break; - case 0x90: // Note On - fluid_synth_noteon(_synth, chan, param1, param2); - break; - case 0xA0: // Aftertouch - break; - case 0xB0: // Control Change - fluid_synth_cc(_synth, chan, param1, param2); - break; - case 0xC0: // Program Change - fluid_synth_program_change(_synth, chan, param1); - break; - case 0xD0: // Channel Pressure - break; - case 0xE0: // Pitch Bend - fluid_synth_pitch_bend(_synth, chan, (param2 << 7) | param1); - break; - case 0xF0: // SysEx - // We should never get here! SysEx information has to be - // sent via high-level semantic methods. - warning("MidiDriver_FluidSynth: Receiving SysEx command on a send() call"); - break; - default: - warning("MidiDriver_FluidSynth: Unknown send() command 0x%02X", cmd); - break; - } -} - -MidiChannel *MidiDriver_FluidSynth::allocateChannel() { - for (int i = 0; i < ARRAYSIZE(_midiChannels); i++) { - if (i != 9 && _midiChannels[i].allocate()) - return &_midiChannels[i]; - } - return NULL; -} - -MidiChannel *MidiDriver_FluidSynth::getPercussionChannel() { - return &_midiChannels[9]; -} - -void MidiDriver_FluidSynth::generateSamples(int16 *data, int len) { - fluid_synth_write_s16(_synth, len, data, 0, 2, data, 1, 2); -} - - -// Plugin interface - -class FluidSynthMusicPlugin : public MusicPluginObject { -public: - const char *getName() const { - return "FluidSynth"; - } - - const char *getId() const { - return "fluidsynth"; - } - - MusicDevices getDevices() const; - Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const; -}; - -MusicDevices FluidSynthMusicPlugin::getDevices() const { - MusicDevices devices; - devices.push_back(MusicDevice(this, "", MT_GM)); - return devices; -} - -Common::Error FluidSynthMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const { - *mididriver = new MidiDriver_FluidSynth(g_system->getMixer()); - - return Common::kNoError; -} - -//#if PLUGIN_ENABLED_DYNAMIC(FLUIDSYNTH) - //REGISTER_PLUGIN_DYNAMIC(FLUIDSYNTH, PLUGIN_TYPE_MUSIC, FluidSynthMusicPlugin); -//#else - REGISTER_PLUGIN_STATIC(FLUIDSYNTH, PLUGIN_TYPE_MUSIC, FluidSynthMusicPlugin); -//#endif - -#endif diff --git a/sound/softsynth/fmtowns_pc98/towns_audio.cpp b/sound/softsynth/fmtowns_pc98/towns_audio.cpp deleted file mode 100644 index 14bc840326..0000000000 --- a/sound/softsynth/fmtowns_pc98/towns_audio.cpp +++ /dev/null @@ -1,1583 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/softsynth/fmtowns_pc98/towns_audio.h" -#include "common/endian.h" -#include "backends/audiocd/audiocd.h" - - -class TownsAudio_PcmChannel { -friend class TownsAudioInterface; -public: - TownsAudio_PcmChannel(); - ~TownsAudio_PcmChannel(); - -private: - void loadExtData(uint8 *buffer, uint32 size); - void setupLoop(uint32 start, uint32 len); - void clear(); - - void envAttack(); - void envDecay(); - void envSustain(); - void envRelease(); - - uint8 *curInstrument; - uint8 note; - uint8 velo; - - int8 *data; - int8 *dataEnd; - - int8 *loopEnd; - uint32 loopLen; - - uint16 stepNote; - uint16 stepPitch; - uint16 step; - - uint8 panLeft; - uint8 panRight; - - uint32 pos; - - uint8 envTotalLevel; - uint8 envAttackRate; - uint8 envDecayRate; - uint8 envSustainLevel; - uint8 envSustainRate; - uint8 envReleaseRate; - - int16 envStep; - int16 envCurrentLevel; - - EnvelopeState envState; - - int8 *extData; -}; - -class TownsAudio_WaveTable { -friend class TownsAudioInterface; -public: - TownsAudio_WaveTable(); - ~TownsAudio_WaveTable(); - -private: - void readHeader(const uint8 *buffer); - void readData(const uint8 *buffer); - void clear(); - - char name[9]; - int32 id; - uint32 size; - uint32 loopStart; - uint32 loopLen; - uint16 rate; - uint16 rateOffs; - uint16 baseNote; - int8 *data; -}; - -TownsAudioInterface::TownsAudioInterface(Audio::Mixer *mixer, TownsAudioInterfacePluginDriver *driver) : TownsPC98_FmSynth(mixer, kTypeTowns), - _fmInstruments(0), _pcmInstruments(0), _pcmChan(0), _waveTables(0), _waveTablesTotalDataSize(0), - _baserate(55125.0f / (float)mixer->getOutputRate()), _tickLength(0), _timer(0), _drv(driver), - _pcmSfxChanMask(0), _musicVolume(Audio::Mixer::kMaxMixerVolume), _sfxVolume(Audio::Mixer::kMaxMixerVolume), - _outputVolumeFlags(0), _outputMuteFlags(0), _pcmChanOut(0), _pcmChanReserved(0), _pcmChanKeyPressed(0), - _pcmChanEffectPlaying(0), _pcmChanKeyPlaying(0), _ready(false) { - -#define INTCB(x) &TownsAudioInterface::intf_##x - static const TownsAudioIntfCallback intfCb[] = { - // 0 - INTCB(reset), - INTCB(keyOn), - INTCB(keyOff), - INTCB(setPanPos), - // 4 - INTCB(setInstrument), - INTCB(loadInstrument), - INTCB(notImpl), - INTCB(setPitch), - // 8 - INTCB(setLevel), - INTCB(chanOff), - INTCB(notImpl), - INTCB(notImpl), - // 12 - INTCB(notImpl), - INTCB(notImpl), - INTCB(notImpl), - INTCB(notImpl), - // 16 - INTCB(notImpl), - INTCB(writeReg), - INTCB(notImpl), - INTCB(writeRegBuffer), - // 20 - INTCB(readRegBuffer), - INTCB(setTimerA), - INTCB(setTimerB), - INTCB(enableTimerA), - // 24 - INTCB(enableTimerB), - INTCB(notImpl), - INTCB(notImpl), - INTCB(notImpl), - // 28 - INTCB(notImpl), - INTCB(notImpl), - INTCB(notImpl), - INTCB(notImpl), - // 32 - INTCB(loadSamples), - INTCB(reserveEffectChannels), - INTCB(loadWaveTable), - INTCB(unloadWaveTable), - // 36 - INTCB(notImpl), - INTCB(pcmPlayEffect), - INTCB(notImpl), - INTCB(pcmChanOff), - // 40 - INTCB(pcmEffectPlaying), - INTCB(notImpl), - INTCB(notImpl), - INTCB(notImpl), - // 44 - INTCB(notImpl), - INTCB(notImpl), - INTCB(notImpl), - INTCB(notImpl), - // 48 - INTCB(notImpl), - INTCB(notImpl), - INTCB(fmKeyOn), - INTCB(fmKeyOff), - // 52 - INTCB(fmSetPanPos), - INTCB(fmSetInstrument), - INTCB(fmLoadInstrument), - INTCB(notImpl), - // 56 - INTCB(fmSetPitch), - INTCB(fmSetLevel), - INTCB(fmReset), - INTCB(notImpl), - // 60 - INTCB(notImpl), - INTCB(notImpl), - INTCB(notImpl), - INTCB(notImpl), - // 64 - INTCB(notImpl), - INTCB(notImpl), - INTCB(notImpl), - INTCB(setOutputVolume), - // 68 - INTCB(resetOutputVolume), - INTCB(notImpl), - INTCB(updateOutputVolume), - INTCB(notImpl), - // 72 - INTCB(notImpl), - INTCB(cdaToggle), - INTCB(notImpl), - INTCB(notImpl), - // 76 - INTCB(notImpl), - INTCB(notImpl), - INTCB(notImpl), - INTCB(notImpl), - // 80 - INTCB(pcmUpdateEnvelopeGenerator), - INTCB(notImpl) - }; -#undef INTCB - - _intfOpcodes = intfCb; - - memset(_fmSaveReg, 0, sizeof(_fmSaveReg)); - memset(_outputLevel, 0, sizeof(_outputLevel)); - - _timerBase = (uint32)(_baserate * 1000000.0f); - _tickLength = 2 * _timerBase; -} - -TownsAudioInterface::~TownsAudioInterface() { - _ready = false; - deinit(); - - delete[] _fmSaveReg[0]; - delete[] _fmSaveReg[1]; - delete[] _fmInstruments; - delete[] _pcmInstruments; - delete[] _waveTables; - delete[] _pcmChan; -} - -bool TownsAudioInterface::init() { - if (_ready) - return true; - - if (!TownsPC98_FmSynth::init()) - return false; - - _fmSaveReg[0] = new uint8[256]; - _fmSaveReg[1] = new uint8[256]; - _fmInstruments = new uint8[128 * 48]; - _pcmInstruments = new uint8[32 * 128]; - _waveTables = new TownsAudio_WaveTable[128]; - _pcmChan = new TownsAudio_PcmChannel[8]; - - _timer = 0; - - setVolumeChannelMasks(-1, 0); - - _ready = true; - callback(0); - - return true; -} - -int TownsAudioInterface::callback(int command, ...) { - if (!_ready) - return 1; - - va_list args; - va_start(args, command); - - if (command > 81) { - va_end(args); - return 4; - } - - int res = (this->*_intfOpcodes[command])(args); - - va_end(args); - return res; -} - -void TownsAudioInterface::setMusicVolume(int volume) { - _musicVolume = CLIP(volume, 0, Audio::Mixer::kMaxMixerVolume); - setVolumeIntern(_musicVolume, _sfxVolume); -} - -void TownsAudioInterface::setSoundEffectVolume(int volume) { - _sfxVolume = CLIP(volume, 0, Audio::Mixer::kMaxMixerVolume); - setVolumeIntern(_musicVolume, _sfxVolume); -} - -void TownsAudioInterface::setSoundEffectChanMask(int mask) { - _pcmSfxChanMask = mask >> 6; - mask &= 0x3f; - setVolumeChannelMasks(~mask, mask); -} - -void TownsAudioInterface::nextTickEx(int32 *buffer, uint32 bufferSize) { - if (!_ready) - return; - - for (uint32 i = 0; i < bufferSize; i++) { - _timer += _tickLength; - while (_timer > 0x514767) { - _timer -= 0x514767; - - for (int ii = 0; ii < 8; ii++) { - if ((_pcmChanKeyPlaying & _chanFlags[ii]) || (_pcmChanEffectPlaying & _chanFlags[ii])) { - TownsAudio_PcmChannel *s = &_pcmChan[ii]; - s->pos += s->step; - - if (&s->data[s->pos >> 11] >= s->loopEnd) { - if (s->loopLen) { - s->pos -= s->loopLen; - } else { - s->pos = 0; - _pcmChanEffectPlaying &= ~_chanFlags[ii]; - _pcmChanKeyPlaying &= ~_chanFlags[ii]; - } - } - } - } - } - - int32 finOutL = 0; - int32 finOutR = 0; - - for (int ii = 0; ii < 8; ii++) { - if (_pcmChanOut & _chanFlags[ii]) { - int32 o = _pcmChan[ii].data[_pcmChan[ii].pos >> 11] * _pcmChan[ii].velo; - if ((1 << ii) & (~_pcmSfxChanMask)) - o = (o * _musicVolume) / Audio::Mixer::kMaxMixerVolume; - if ((1 << ii) & _pcmSfxChanMask) - o = (o * _sfxVolume) / Audio::Mixer::kMaxMixerVolume; - if (_pcmChan[ii].panLeft) - finOutL += ((o * _pcmChan[ii].panLeft) >> 3); - if (_pcmChan[ii].panRight) - finOutR += ((o * _pcmChan[ii].panRight) >> 3); - if (!((_pcmChanKeyPlaying & _chanFlags[ii]) || (_pcmChanEffectPlaying & _chanFlags[ii]))) - _pcmChanOut &= ~_chanFlags[ii]; - } - } - - buffer[i << 1] += finOutL; - buffer[(i << 1) + 1] += finOutR; - } -} - -void TownsAudioInterface::timerCallbackA() { - Common::StackLock lock(_mutex); - if (_drv && _ready) - _drv->timerCallback(0); -} - -void TownsAudioInterface::timerCallbackB() { - Common::StackLock lock(_mutex); - if (_ready) { - if (_drv) - _drv->timerCallback(1); - callback(80); - } -} - -int TownsAudioInterface::intf_reset(va_list &args) { - fmReset(); - pcmReset(); - callback(68); - return 0; -} - -int TownsAudioInterface::intf_keyOn(va_list &args) { - int chan = va_arg(args, int); - int note = va_arg(args, int); - int velo = va_arg(args, int); - return (chan & 0x40) ? pcmKeyOn(chan, note, velo) : fmKeyOn(chan, note, velo); -} - -int TownsAudioInterface::intf_keyOff(va_list &args) { - int chan = va_arg(args, int); - return (chan & 0x40) ? pcmKeyOff(chan) : fmKeyOff(chan); -} - -int TownsAudioInterface::intf_setPanPos(va_list &args) { - int chan = va_arg(args, int); - int mode = va_arg(args, int); - return (chan & 0x40) ? pcmSetPanPos(chan, mode) : fmSetPanPos(chan, mode); -} - -int TownsAudioInterface::intf_setInstrument(va_list &args) { - int chan = va_arg(args, int); - int instrId = va_arg(args, int); - return (chan & 0x40) ? pcmSetInstrument(chan, instrId) : fmSetInstrument(chan, instrId); -} - -int TownsAudioInterface::intf_loadInstrument(va_list &args) { - int chanType = va_arg(args, int); - int instrId = va_arg(args, int); - uint8 *instrData = va_arg(args, uint8 *); - return (chanType & 0x40) ? pcmLoadInstrument(instrId, instrData) : fmLoadInstrument(instrId, instrData); -} - -int TownsAudioInterface::intf_setPitch(va_list &args) { - int chan = va_arg(args, int); - int16 pitch = (int16)(va_arg(args, int) & 0xffff); - return (chan & 0x40) ? pcmSetPitch(chan, pitch) : fmSetPitch(chan, pitch); -} - -int TownsAudioInterface::intf_setLevel(va_list &args) { - int chan = va_arg(args, int); - int lvl = va_arg(args, int); - return (chan & 0x40) ? pcmSetLevel(chan, lvl) : fmSetLevel(chan, lvl); -} - -int TownsAudioInterface::intf_chanOff(va_list &args) { - int chan = va_arg(args, int); - return (chan & 0x40) ? pcmChanOff(chan) : fmChanOff(chan); -} - -int TownsAudioInterface::intf_writeReg(va_list &args) { - int part = va_arg(args, int) ? 1 : 0; - int reg = va_arg(args, int); - int val = va_arg(args, int); - if ((!part && reg < 0x20) || (part && reg < 0x30) || (reg > 0xb6)) - return 3; - - bufferedWriteReg(part, reg, val); - return 0; -} - -int TownsAudioInterface::intf_writeRegBuffer(va_list &args) { - int part = va_arg(args, int) ? 1 : 0; - int reg = va_arg(args, int); - int val = va_arg(args, int); - - if ((!part && reg < 0x20) || (part && reg < 0x30) || (reg > 0xef)) - return 3; - - _fmSaveReg[part][reg] = val; - return 0; -} - -int TownsAudioInterface::intf_readRegBuffer(va_list &args) { - int part = va_arg(args, int) ? 1 : 0; - int reg = va_arg(args, int); - uint8 *dst = va_arg(args, uint8 *); - *dst = 0; - - if ((!part && reg < 0x20) || (part && reg < 0x30) || (reg > 0xef)) - return 3; - - *dst = _fmSaveReg[part][reg]; - return 0; -} - -int TownsAudioInterface::intf_setTimerA(va_list &args) { - int enable = va_arg(args, int); - int tempo = va_arg(args, int); - - if (enable) { - bufferedWriteReg(0, 0x25, tempo & 3); - bufferedWriteReg(0, 0x24, (tempo >> 2) & 0xff); - bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x05); - } else { - bufferedWriteReg(0, 0x27, (_fmSaveReg[0][0x27] & 0xfa) | 0x10); - } - - return 0; -} - -int TownsAudioInterface::intf_setTimerB(va_list &args) { - int enable = va_arg(args, int); - int tempo = va_arg(args, int); - - if (enable) { - bufferedWriteReg(0, 0x26, tempo & 0xff); - bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x0A); - } else { - bufferedWriteReg(0, 0x27, (_fmSaveReg[0][0x27] & 0xf5) | 0x20); - } - - return 0; -} - -int TownsAudioInterface::intf_enableTimerA(va_list &args) { - bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x15); - return 0; -} - -int TownsAudioInterface::intf_enableTimerB(va_list &args) { - bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x2a); - return 0; -} - -int TownsAudioInterface::intf_loadSamples(va_list &args) { - uint32 dest = va_arg(args, uint32); - int size = va_arg(args, int); - uint8 *src = va_arg(args, uint8*); - - if (dest >= 65536 || size == 0 || size > 65536) - return 3; - if (size + dest > 65536) - return 5; - - int dwIndex = _numWaveTables - 1; - for (uint32 t = _waveTablesTotalDataSize; dwIndex && (dest < t); dwIndex--) - t -= _waveTables[dwIndex].size; - - TownsAudio_WaveTable *s = &_waveTables[dwIndex]; - _waveTablesTotalDataSize -= s->size; - s->size = size; - s->readData(src); - _waveTablesTotalDataSize += s->size; - - return 0; -} - -int TownsAudioInterface::intf_reserveEffectChannels(va_list &args) { - int numChan = va_arg(args, int); - if (numChan > 8) - return 3; - if ((numChan << 13) + _waveTablesTotalDataSize > 65536) - return 5; - - if (numChan == _numReservedChannels) - return 0; - - if (numChan < _numReservedChannels) { - int c = 8 - _numReservedChannels; - for (int i = numChan; i; i--) { - uint8 f = ~_chanFlags[c--]; - _pcmChanEffectPlaying &= f; - } - } else { - int c = 7 - _numReservedChannels; - for (int i = numChan - _numReservedChannels; i; i--) { - uint8 f = ~_chanFlags[c--]; - _pcmChanKeyPressed &= f; - _pcmChanKeyPlaying &= f; - } - } - - static const uint8 reserveChanFlags[] = { 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF }; - _numReservedChannels = numChan; - _pcmChanReserved = reserveChanFlags[_numReservedChannels]; - - return 0; -} - -int TownsAudioInterface::intf_loadWaveTable(va_list &args) { - uint8 *data = va_arg(args, uint8 *); - if (_numWaveTables > 127) - return 3; - - TownsAudio_WaveTable w; - w.readHeader(data); - if (!w.size) - return 6; - - if (_waveTablesTotalDataSize + w.size > 65504) - return 5; - - for (int i = 0; i < _numWaveTables; i++) { - if (_waveTables[i].id == w.id) - return 10; - } - - TownsAudio_WaveTable *s = &_waveTables[_numWaveTables++]; - s->readHeader(data); - - _waveTablesTotalDataSize += s->size; - callback(32, _waveTablesTotalDataSize, s->size, data + 32); - - return 0; -} - -int TownsAudioInterface::intf_unloadWaveTable(va_list &args) { - int id = va_arg(args, int); - - if (id == -1) { - for (int i = 0; i < 128; i++) - _waveTables[i].clear(); - _numWaveTables = 0; - _waveTablesTotalDataSize = 0; - } else { - if (_waveTables) { - for (int i = 0; i < _numWaveTables; i++) { - if (_waveTables[i].id == id) { - _numWaveTables--; - _waveTablesTotalDataSize -= _waveTables[i].size; - _waveTables[i].clear(); - for (; i < _numWaveTables; i++) - memcpy(&_waveTables[i], &_waveTables[i + 1], sizeof(TownsAudio_WaveTable)); - return 0; - } - return 9; - } - } - } - - return 0; -} - -int TownsAudioInterface::intf_pcmPlayEffect(va_list &args) { - int chan = va_arg(args, int); - int note = va_arg(args, int); - int velo = va_arg(args, int); - uint8 *data = va_arg(args, uint8 *); - - if (chan < 0x40 || chan > 0x47) - return 1; - - if (note & 0x80 || velo & 0x80) - return 3; - - chan -= 0x40; - - if (!(_pcmChanReserved & _chanFlags[chan])) - return 7; - - if ((_pcmChanEffectPlaying & _chanFlags[chan])) - return 2; - - TownsAudio_WaveTable w; - w.readHeader(data); - - if (w.size < (w.loopStart + w.loopLen)) - return 13; - - if (!w.size) - return 6; - - TownsAudio_PcmChannel *p = &_pcmChan[chan]; - - _pcmChanNote[chan] = note; - _pcmChanVelo[chan] = velo; - - p->note = note; - p->velo = velo << 1; - - p->loadExtData(data + 32, w.size); - p->setupLoop(w.loopStart, w.loopLen); - - pcmCalcPhaseStep(p, &w); - if (p->step > 2048) - p->step = 2048; - - _pcmChanEffectPlaying |= _chanFlags[chan]; - _pcmChanOut |= _chanFlags[chan]; - - return 0; -} - -int TownsAudioInterface::intf_pcmChanOff(va_list &args) { - int chan = va_arg(args, int); - pcmChanOff(chan); - return 0; -} - -int TownsAudioInterface::intf_pcmEffectPlaying(va_list &args) { - int chan = va_arg(args, int); - if (chan < 0x40 || chan > 0x47) - return 1; - chan -= 0x40; - return (_pcmChanEffectPlaying & _chanFlags[chan]) ? 1 : 0; -} - -int TownsAudioInterface::intf_fmKeyOn(va_list &args) { - int chan = va_arg(args, int); - int note = va_arg(args, int); - int velo = va_arg(args, int); - return fmKeyOn(chan, note, velo); -} - -int TownsAudioInterface::intf_fmKeyOff(va_list &args) { - int chan = va_arg(args, int); - return fmKeyOff(chan); -} - -int TownsAudioInterface::intf_fmSetPanPos(va_list &args) { - int chan = va_arg(args, int); - int mode = va_arg(args, int); - return fmSetPanPos(chan, mode); -} - -int TownsAudioInterface::intf_fmSetInstrument(va_list &args) { - int chan = va_arg(args, int); - int instrId = va_arg(args, int); - return fmSetInstrument(chan, instrId); -} - -int TownsAudioInterface::intf_fmLoadInstrument(va_list &args) { - int instrId = va_arg(args, int); - uint8 *instrData = va_arg(args, uint8 *); - return fmLoadInstrument(instrId, instrData); -} - -int TownsAudioInterface::intf_fmSetPitch(va_list &args) { - int chan = va_arg(args, int); - uint16 freq = va_arg(args, int) & 0xffff; - return fmSetPitch(chan, freq); -} - -int TownsAudioInterface::intf_fmSetLevel(va_list &args) { - int chan = va_arg(args, int); - int lvl = va_arg(args, int); - return fmSetLevel(chan, lvl); -} - -int TownsAudioInterface::intf_fmReset(va_list &args) { - fmReset(); - return 0; -} - -int TownsAudioInterface::intf_setOutputVolume(va_list &args) { - int chanType = va_arg(args, int); - int left = va_arg(args, int); - int right = va_arg(args, int); - - if (left & 0xff80 || right & 0xff80) - return 3; - - static const uint8 flags[] = { 0x0C, 0x30, 0x40, 0x80 }; - - uint8 chan = (chanType & 0x40) ? 8 : 12; - - chanType &= 3; - left = (left & 0x7e) >> 1; - right = (right & 0x7e) >> 1; - - if (chan) - _outputVolumeFlags |= flags[chanType]; - else - _outputVolumeFlags &= ~flags[chanType]; - - if (chanType > 1) { - _outputLevel[chan + chanType] = left; - } else { - if (chanType == 0) - chan -= 8; - _outputLevel[chan] = left; - _outputLevel[chan + 1] = right; - } - - updateOutputVolume(); - - return 0; -} - -int TownsAudioInterface::intf_resetOutputVolume(va_list &args) { - memset(_outputLevel, 0, sizeof(_outputLevel)); - _outputMuteFlags = 0; - _outputVolumeFlags = 0; - updateOutputVolume(); - return 0; -} - -int TownsAudioInterface::intf_updateOutputVolume(va_list &args) { - int flags = va_arg(args, int); - _outputMuteFlags = flags & 3; - updateOutputVolume(); - return 0; -} - -int TownsAudioInterface::intf_cdaToggle(va_list &args) { - //int mode = va_arg(args, int); - //_unkMask = mode ? 0x7f : 0x3f; - return 0; -} - -int TownsAudioInterface::intf_pcmUpdateEnvelopeGenerator(va_list &args) { - for (int i = 0; i < 8; i++) - pcmUpdateEnvelopeGenerator(i); - return 0; -} - -int TownsAudioInterface::intf_notImpl(va_list &args) { - return 4; -} - -void TownsAudioInterface::fmReset() { - TownsPC98_FmSynth::reset(); - - _fmChanPlaying = 0; - memset(_fmChanNote, 0, sizeof(_fmChanNote)); - memset(_fmChanPitch, 0, sizeof(_fmChanPitch)); - - memset(_fmSaveReg[0], 0, 240); - memset(&_fmSaveReg[0][240], 0x7f, 16); - memset(_fmSaveReg[1], 0, 256); - memset(&_fmSaveReg[1][240], 0x7f, 16); - _fmSaveReg[0][243] = _fmSaveReg[0][247] = _fmSaveReg[0][251] = _fmSaveReg[0][255] = _fmSaveReg[1][243] = _fmSaveReg[1][247] = _fmSaveReg[1][251] = _fmSaveReg[1][255] = 0xff; - - for (int i = 0; i < 128; i++) - fmLoadInstrument(i, _fmDefaultInstrument); - - bufferedWriteReg(0, 0x21, 0); - bufferedWriteReg(0, 0x2C, 0x80); - bufferedWriteReg(0, 0x2B, 0); - bufferedWriteReg(0, 0x27, 0x30); - - for (int i = 0; i < 6; i++) { - fmKeyOff(i); - fmSetInstrument(i, 0); - fmSetLevel(i, 127); - } -} - -int TownsAudioInterface::fmKeyOn(int chan, int note, int velo) { - if (chan > 5) - return 1; - if (note < 12 || note > 107 || (velo & 0x80)) - return 3; - if (_fmChanPlaying & _chanFlags[chan]) - return 2; - - _fmChanPlaying |= _chanFlags[chan]; - note -= 12; - - _fmChanNote[chan] = note; - int16 pitch = _fmChanPitch[chan]; - - uint8 part = chan > 2 ? 1 : 0; - if (chan > 2) - chan -= 3; - - int frq = 0; - uint8 bl = 0; - - if (note) { - frq = _frequency[(note - 1) % 12]; - bl = (note - 1) / 12; - } else { - frq = 616; - } - - frq += pitch; - - if (frq < 616) { - if (!bl) { - frq = 616; - } else { - frq += 616; - --bl; - } - } else if (frq > 1232) { - if (bl == 7) { - frq = 15500; - } else { - frq -= 616; - ++bl; - } - } - - frq |= (bl << 11); - - bufferedWriteReg(part, chan + 0xa4, (frq >> 8) & 0xff); - bufferedWriteReg(part, chan + 0xa0, frq & 0xff); - - velo = (velo >> 2) + 96; - uint16 c = _carrier[_fmSaveReg[part][0xb0 + chan] & 7]; - _fmSaveReg[part][0xe0 + chan] = velo; - - for (uint8 reg = 0x40 + chan; reg < 0x50; reg += 4) { - c += c; - if (c & 0x100) { - c &= 0xff; - bufferedWriteReg(part, reg, (((((((_fmSaveReg[part][0x80 + reg] ^ 0x7f) * velo) >> 7) + 1) * _fmSaveReg[part][0xd0 + chan]) >> 7) + 1) ^ 0x7f); - } - } - - uint8 v = chan; - if (part) - v |= 4; - - for (uint8 reg = 0x80 + chan; reg < 0x90; reg += 4) - writeReg(part, reg, _fmSaveReg[part][reg] | 0x0f); - - writeReg(0, 0x28, v); - - for (uint8 reg = 0x80 + chan; reg < 0x90; reg += 4) - writeReg(part, reg, _fmSaveReg[part][reg]); - - bufferedWriteReg(0, 0x28, v | 0xf0); - - return 0; -} - -int TownsAudioInterface::fmKeyOff(int chan) { - if (chan > 5) - return 1; - _fmChanPlaying &= ~_chanFlags[chan]; - if (chan > 2) - chan++; - bufferedWriteReg(0, 0x28, chan); - return 0; -} - -int TownsAudioInterface::fmChanOff(int chan) { - if (chan > 5) - return 1; - _fmChanPlaying &= ~_chanFlags[chan]; - - uint8 part = chan > 2 ? 1 : 0; - if (chan > 2) - chan -= 3; - - for (uint8 reg = 0x80 + chan; reg < 0x90; reg += 4) - writeReg(part, reg, _fmSaveReg[part][reg] | 0x0f); - - if (part) - chan += 4; - writeReg(0, 0x28, chan); - return 0; -} - -int TownsAudioInterface::fmSetPanPos(int chan, int value) { - if (chan > 5) - return 1; - - uint8 part = chan > 2 ? 1 : 0; - if (chan > 2) - chan -= 3; - - if (value > 0x40) - value = 0x40; - else if (value < 0x40) - value = 0x80; - else - value = 0xC0; - - bufferedWriteReg(part, 0xb4 + chan, (_fmSaveReg[part][0xb4 + chan] & 0x3f) | value); - return 0; -} - -int TownsAudioInterface::fmSetInstrument(int chan, int instrId) { - if (chan > 5) - return 1; - if (instrId > 127) - return 3; - - uint8 part = chan > 2 ? 1 : 0; - if (chan > 2) - chan -= 3; - - uint8 *src = &_fmInstruments[instrId * 48 + 8]; - - uint16 c = _carrier[src[24] & 7]; - uint8 reg = 0x30 + chan; - - for (; reg < 0x40; reg += 4) - bufferedWriteReg(part, reg, *src++); - - for (; reg < 0x50; reg += 4) { - uint8 v = *src++; - _fmSaveReg[part][0x80 + reg] = _fmSaveReg[part][reg] = v; - c += c; - if (c & 0x100) { - c &= 0xff; - v = 127; - } - writeReg(part, reg, v); - } - - for (; reg < 0x90; reg += 4) - bufferedWriteReg(part, reg, *src++); - - reg += 0x20; - bufferedWriteReg(part, reg, *src++); - - uint8 v = *src++; - reg += 4; - if (v < 64) - v |= (_fmSaveReg[part][reg] & 0xc0); - bufferedWriteReg(part, reg, v); - - return 0; -} - -int TownsAudioInterface::fmLoadInstrument(int instrId, const uint8 *data) { - if (instrId > 127) - return 3; - assert(data); - memcpy(&_fmInstruments[instrId * 48], data, 48); - return 0; -} - -int TownsAudioInterface::fmSetPitch(int chan, int pitch) { - if (chan > 5) - return 1; - - uint8 bl = _fmChanNote[chan]; - int frq = 0; - - if (pitch < 0) { - if (bl) { - if (pitch < -8008) - pitch = -8008; - pitch *= -1; - pitch /= 13; - frq = _frequency[(bl - 1) % 12] - pitch; - bl = (bl - 1) / 12; - _fmChanPitch[chan] = -pitch; - - if (frq < 616) { - if (bl) { - frq += 616; - bl--; - } else { - frq = 616; - bl = 0; - } - } - } else { - frq = 616; - bl = 0; - } - - } else if (pitch > 0) { - if (bl < 96) { - if (pitch > 8008) - pitch = 8008; - pitch /= 13; - - if (bl) { - frq = _frequency[(bl - 1) % 12] + pitch; - bl = (bl - 1) / 12; - } else { - frq = 616; - bl = 0; - } - - _fmChanPitch[chan] = pitch; - - if (frq > 1232) { - if (bl < 7) { - frq -= 616; - bl++; - } else { - frq = 1164; - bl = 7; - } - } else { - if (bl >= 7 && frq > 1164) - frq = 1164; - } - - } else { - frq = 1164; - bl = 7; - } - } else { - _fmChanPitch[chan] = 0; - if (bl) { - frq = _frequency[(bl - 1) % 12]; - bl = (bl - 1) / 12; - } else { - frq = 616; - bl = 0; - } - } - - uint8 part = chan > 2 ? 1 : 0; - if (chan > 2) - chan -= 3; - - frq |= (bl << 11); - - bufferedWriteReg(part, chan + 0xa4, (frq >> 8)); - bufferedWriteReg(part, chan + 0xa0, (frq & 0xff)); - - return 0; -} - -int TownsAudioInterface::fmSetLevel(int chan, int lvl) { - if (chan > 5) - return 1; - if (lvl > 127) - return 3; - - uint8 part = chan > 2 ? 1 : 0; - if (chan > 2) - chan -= 3; - - uint16 c = _carrier[_fmSaveReg[part][0xb0 + chan] & 7]; - _fmSaveReg[part][0xd0 + chan] = lvl; - - for (uint8 reg = 0x40 + chan; reg < 0x50; reg += 4) { - c += c; - if (c & 0x100) { - c &= 0xff; - bufferedWriteReg(part, reg, (((((((_fmSaveReg[part][0x80 + reg] ^ 0x7f) * lvl) >> 7) + 1) * _fmSaveReg[part][0xe0 + chan]) >> 7) + 1) ^ 0x7f); - } - } - return 0; -} - -void TownsAudioInterface::bufferedWriteReg(uint8 part, uint8 regAddress, uint8 value) { - _fmSaveReg[part][regAddress] = value; - writeReg(part, regAddress, value); -} - -void TownsAudioInterface::pcmReset() { - _pcmChanOut = 0; - _pcmChanReserved = _pcmChanKeyPressed = _pcmChanEffectPlaying = _pcmChanKeyPlaying = 0; - _numReservedChannels = 0; - - memset(_pcmChanNote, 0, 8); - memset(_pcmChanVelo, 0, 8); - memset(_pcmChanLevel, 0, 8); - - for (int i = 0; i < 8; i++) - _pcmChan[i].clear(); - - memset(_pcmInstruments, 0, 128 * 32); - static uint8 name[] = { 0x4E, 0x6F, 0x20, 0x44, 0x61, 0x74, 0x61, 0x21 }; - for (int i = 0; i < 32; i++) - memcpy(_pcmInstruments + i * 128, name, 8); - - for (int i = 0; i < 128; i++) - _waveTables[i].clear(); - _numWaveTables = 0; - _waveTablesTotalDataSize = 0; - - for (int i = 0x40; i < 0x48; i++) { - pcmSetInstrument(i, 0); - pcmSetLevel(i, 127); - } -} - -int TownsAudioInterface::pcmKeyOn(int chan, int note, int velo) { - if (chan < 0x40 || chan > 0x47) - return 1; - - if (note & 0x80 || velo & 0x80) - return 3; - - chan -= 0x40; - - if ((_pcmChanReserved & _chanFlags[chan]) || (_pcmChanKeyPressed & _chanFlags[chan])) - return 2; - - _pcmChanNote[chan] = note; - _pcmChanVelo[chan] = velo; - - TownsAudio_PcmChannel *p = &_pcmChan[chan]; - p->note = note; - - uint8 *instr = _pcmChan[chan].curInstrument; - int i = 0; - for (; i < 8; i++) { - if (note <= instr[16 + 2 * i]) - break; - } - - if (i == 8) - return 8; - - int il = i << 3; - p->note += instr[il + 70]; - - p->envTotalLevel = instr[il + 64]; - p->envAttackRate = instr[il + 65]; - p->envDecayRate = instr[il + 66]; - p->envSustainLevel = instr[il + 67]; - p->envSustainRate = instr[il + 68]; - p->envReleaseRate = instr[il + 69]; - p->envStep = 0; - - int32 id = (int32)READ_LE_UINT32(&instr[i * 4 + 32]); - - for (i = 0; i < _numWaveTables; i++) { - if (id == _waveTables[i].id) - break; - } - - if (i == _numWaveTables) - return 9; - - TownsAudio_WaveTable *w = &_waveTables[i]; - - p->data = w->data; - p->dataEnd = w->data + w->size; - p->setupLoop(w->loopStart, w->loopLen); - - pcmCalcPhaseStep(p, w); - - uint32 lvl = _pcmChanLevel[chan] * _pcmChanVelo[chan]; - p->envTotalLevel = ((p->envTotalLevel * lvl) >> 14) & 0xff; - p->envSustainLevel = ((p->envSustainLevel * lvl) >> 14) & 0xff; - - p->envAttack(); - p->velo = (p->envCurrentLevel >> 8) << 1; - - _pcmChanKeyPressed |= _chanFlags[chan]; - _pcmChanKeyPlaying |= _chanFlags[chan]; - _pcmChanOut |= _chanFlags[chan]; - - return 0; -} - -int TownsAudioInterface::pcmKeyOff(int chan) { - if (chan < 0x40 || chan > 0x47) - return 1; - - chan -= 0x40; - _pcmChanKeyPressed &= ~_chanFlags[chan]; - _pcmChan[chan].envRelease(); - return 0; -} - -int TownsAudioInterface::pcmChanOff(int chan) { - if (chan < 0x40 || chan > 0x47) - return 1; - - chan -= 0x40; - - _pcmChanKeyPressed &= ~_chanFlags[chan]; - _pcmChanEffectPlaying &= ~_chanFlags[chan]; - _pcmChanKeyPlaying &= ~_chanFlags[chan]; - _pcmChanOut &= ~_chanFlags[chan]; - - return 0; -} - -int TownsAudioInterface::pcmSetPanPos(int chan, int mode) { - if (chan > 0x47) - return 1; - if (mode & 0x80) - return 3; - - chan -= 0x40; - uint8 blc = 0x77; - - if (mode > 64) { - mode -= 64; - blc = ((blc ^ (mode >> 3)) + (mode << 4)) & 0xff; - } else if (mode < 64) { - mode = (mode >> 3) ^ 7; - blc = ((119 + mode) ^ (mode << 4)) & 0xff; - } - - _pcmChan[chan].panLeft = blc & 0x0f; - _pcmChan[chan].panRight = blc >> 4; - - return 0; -} - -int TownsAudioInterface::pcmSetInstrument(int chan, int instrId) { - if (chan > 0x47) - return 1; - if (instrId > 31) - return 3; - chan -= 0x40; - _pcmChan[chan].curInstrument = &_pcmInstruments[instrId * 128]; - return 0; -} - -int TownsAudioInterface::pcmLoadInstrument(int instrId, const uint8 *data) { - if (instrId > 31) - return 3; - assert(data); - memcpy(&_pcmInstruments[instrId * 128], data, 128); - return 0; -} - -int TownsAudioInterface::pcmSetPitch(int chan, int pitch) { - if (chan > 0x47) - return 1; - - if (pitch < -8192 || pitch > 8191) - return 3; - - chan -= 0x40; - TownsAudio_PcmChannel *p = &_pcmChan[chan]; - - uint32 pts = 0x4000; - - if (pitch < 0) - pts = (0x20000000 / (-pitch + 0x2001)) >> 2; - else if (pitch > 0) - pts = (((pitch + 0x2001) << 16) / 0x2000) >> 2; - - p->stepPitch = pts & 0xffff; - p->step = (p->stepNote * p->stepPitch) >> 14; - -// if (_pcmChanUnkFlag & _chanFlags[chan]) -// unk[chan] = (((p->step * 1000) << 11) / 98) / 20833; - - /*else*/ - if ((_pcmChanEffectPlaying & _chanFlags[chan]) && (p->step > 2048)) - p->step = 2048; - - return 0; -} - -int TownsAudioInterface::pcmSetLevel(int chan, int lvl) { - if (chan > 0x47) - return 1; - - if (lvl & 0x80) - return 3; - - chan -= 0x40; - TownsAudio_PcmChannel *p = &_pcmChan[chan]; - - if (_pcmChanReserved & _chanFlags[chan]) { - _pcmChanVelo[chan] = lvl; - p->velo = lvl << 1; - } else { - int32 t = p->envStep * lvl; - if (_pcmChanLevel[chan]) - t /= _pcmChanLevel[chan]; - p->envStep = t; - t = p->envCurrentLevel * lvl; - if (_pcmChanLevel[chan]) - t /= _pcmChanLevel[chan]; - p->envCurrentLevel = t; - _pcmChanLevel[chan] = lvl; - p->velo = p->envCurrentLevel >> 8; - } - - return 0; -} - -void TownsAudioInterface::pcmUpdateEnvelopeGenerator(int chan) { - TownsAudio_PcmChannel *p = &_pcmChan[chan]; - if (!p->envCurrentLevel) { - _pcmChanKeyPlaying &= ~_chanFlags[chan]; - p->envState = kEnvReady; - } - - if (!(_pcmChanKeyPlaying & _chanFlags[chan])) - return; - - switch (p->envState) { - case kEnvAttacking: - if (((p->envCurrentLevel + p->envStep) >> 8) > p->envTotalLevel) { - p->envDecay(); - return; - } else { - p->envCurrentLevel += p->envStep; - } - break; - - case kEnvDecaying: - if (((p->envCurrentLevel - p->envStep) >> 8) < p->envSustainLevel) { - p->envSustain(); - return; - } else { - p->envCurrentLevel -= p->envStep; - } - break; - - case kEnvSustaining: - case kEnvReleasing: - p->envCurrentLevel -= p->envStep; - if (p->envCurrentLevel <= 0) - p->envCurrentLevel = 0; - break; - - default: - break; - } - p->velo = (p->envCurrentLevel >> 8) << 1; -} - -void TownsAudioInterface::pcmCalcPhaseStep(TownsAudio_PcmChannel *p, TownsAudio_WaveTable *w) { - int8 diff = p->note - w->baseNote; - uint16 r = w->rate + w->rateOffs; - uint16 bl = 0; - uint32 s = 0; - - if (diff < 0) { - diff *= -1; - bl = diff % 12; - diff /= 12; - s = (r >> diff); - if (bl) - s = (s * _pcmPhase2[bl]) >> 16; - - } else if (diff > 0) { - bl = diff % 12; - diff /= 12; - s = (r << diff); - if (bl) - s += ((s * _pcmPhase1[bl]) >> 16); - - } else { - s = r; - } - - p->stepNote = s & 0xffff; - p->step = (s * p->stepPitch) >> 14; -} - -void TownsAudioInterface::updateOutputVolume() { - // FM Towns seems to support volumes of 0 - 63 for each channel. - // We recalculate sane values for our 0 to 255 volume range and - // balance values for our -128 to 127 volume range - - // CD-AUDIO - uint32 maxVol = MAX(_outputLevel[12], _outputLevel[13]); - - int volume = (int)(((float)(maxVol * 255) / 63.0f)); - int balance = maxVol ? (int)( ( ((int)_outputLevel[13] - _outputLevel[12]) * 127) / (float)maxVol) : 0; - - g_system->getAudioCDManager()->setVolume(volume); - g_system->getAudioCDManager()->setBalance(balance); -} - -const uint8 TownsAudioInterface::_chanFlags[] = { - 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 -}; - -const uint16 TownsAudioInterface::_frequency[] = { - 0x028C, 0x02B4, 0x02DC, 0x030A, 0x0338, 0x0368, 0x039C, 0x03D4, 0x040E, 0x044A, 0x048C, 0x04D0 -}; - -const uint8 TownsAudioInterface::_carrier[] = { - 0x10, 0x10, 0x10, 0x10, 0x30, 0x70, 0x70, 0xF0 -}; - -const uint8 TownsAudioInterface::_fmDefaultInstrument[] = { - 0x45, 0x4C, 0x45, 0x50, 0x49, 0x41, 0x4E, 0x4F, 0x01, 0x0A, 0x02, 0x01, - 0x1E, 0x32, 0x05, 0x00, 0x9C, 0xDC, 0x9C, 0xDC, 0x07, 0x03, 0x14, 0x08, - 0x00, 0x03, 0x05, 0x05, 0x55, 0x45, 0x27, 0xA7, 0x04, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 -}; - -const uint16 TownsAudioInterface::_pcmPhase1[] = { - 0x879B, 0x0F37, 0x1F58, 0x306E, 0x4288, 0x55B6, 0x6A08, 0x7F8F, 0x965E, 0xAE88, 0xC882, 0xE341 -}; - -const uint16 TownsAudioInterface::_pcmPhase2[] = { - 0xFEFE, 0xF1A0, 0xE411, 0xD744, 0xCB2F, 0xBFC7, 0xB504, 0xAAE2, 0xA144, 0x9827, 0x8FAC -}; - -TownsAudio_PcmChannel::TownsAudio_PcmChannel() { - extData = 0; - clear(); -} - -TownsAudio_PcmChannel::~TownsAudio_PcmChannel() { - clear(); -} - -void TownsAudio_PcmChannel::loadExtData(uint8 *buffer, uint32 size) { - delete[] extData; - extData = new int8[size]; - int8 *src = (int8 *)buffer; - int8 *dst = extData; - for (uint32 i = 0; i < size; i++) - *dst++ = *src & 0x80 ? (*src++ & 0x7f) : -*src++; - - data = extData; - dataEnd = extData + size; - pos = 0; -} - -void TownsAudio_PcmChannel::setupLoop(uint32 start, uint32 len) { - loopLen = len << 11; - loopEnd = loopLen ? &data[(start + loopLen) >> 11] : dataEnd; - pos = start; -} - -void TownsAudio_PcmChannel::clear() { - curInstrument = 0; - note = 0; - velo = 0; - - data = 0; - dataEnd = 0; - loopLen = 0; - - pos = 0; - loopEnd = 0; - - step = 0; - stepNote = 0x4000; - stepPitch = 0x4000; - - panLeft = panRight = 7; - - envTotalLevel = envAttackRate = envDecayRate = envSustainLevel = envSustainRate = envReleaseRate = 0; - envStep = envCurrentLevel = 0; - - envState = kEnvReady; - - delete[] extData; - extData = 0; -} - -void TownsAudio_PcmChannel::envAttack() { - envState = kEnvAttacking; - int16 t = envTotalLevel << 8; - if (envAttackRate == 127) { - envStep = 0; - } else if (envAttackRate) { - envStep = t / envAttackRate; - envCurrentLevel = 1; - } else { - envCurrentLevel = t; - envDecay(); - } -} - -void TownsAudio_PcmChannel::envDecay() { - envState = kEnvDecaying; - int16 t = envTotalLevel - envSustainLevel; - if (t < 0 || envDecayRate == 127) { - envStep = 0; - } else if (envDecayRate) { - envStep = (t << 8) / envDecayRate; - } else { - envCurrentLevel = envSustainLevel << 8; - envSustain(); - } -} - -void TownsAudio_PcmChannel::envSustain() { - envState = kEnvSustaining; - if (envSustainLevel && envSustainRate) - envStep = (envSustainRate == 127) ? 0 : (envCurrentLevel / envSustainRate) >> 1; - else - envStep = envCurrentLevel = 1; -} - -void TownsAudio_PcmChannel::envRelease() { - envState = kEnvReleasing; - if (envReleaseRate == 127) - envStep = 0; - else if (envReleaseRate) - envStep = envCurrentLevel / envReleaseRate; - else - envStep = envCurrentLevel = 1; -} - -TownsAudio_WaveTable::TownsAudio_WaveTable() { - data = 0; - clear(); -} - -TownsAudio_WaveTable::~TownsAudio_WaveTable() { - clear(); -} - -void TownsAudio_WaveTable::readHeader(const uint8 *buffer) { - memcpy(name, buffer, 8); - name[8] = 0; - id = READ_LE_UINT32(&buffer[8]); - size = READ_LE_UINT32(&buffer[12]); - loopStart = READ_LE_UINT32(&buffer[16]); - loopLen = READ_LE_UINT32(&buffer[20]); - rate = READ_LE_UINT16(&buffer[24]); - rateOffs = READ_LE_UINT16(&buffer[26]); - baseNote = READ_LE_UINT32(&buffer[28]); -} - -void TownsAudio_WaveTable::readData(const uint8 *buffer) { - if (!size) - return; - - delete[] data; - data = new int8[size]; - - const int8 *src = (const int8 *)buffer; - int8 *dst = data; - for (uint32 i = 0; i < size; i++) - *dst++ = *src & 0x80 ? (*src++ & 0x7f) : -*src++; -} - -void TownsAudio_WaveTable::clear() { - name[0] = name[8] = 0; - id = -1; - size = 0; - loopStart = 0; - loopLen = 0; - rate = 0; - rateOffs = 0; - baseNote = 0; - delete[] data; - data = 0; -} - diff --git a/sound/softsynth/fmtowns_pc98/towns_audio.h b/sound/softsynth/fmtowns_pc98/towns_audio.h deleted file mode 100644 index 95fb1ded59..0000000000 --- a/sound/softsynth/fmtowns_pc98/towns_audio.h +++ /dev/null @@ -1,179 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef TOWNS_AUDIO_H -#define TOWNS_AUDIO_H - -#include "sound/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h" - -class TownsAudioInterfacePluginDriver { -public: - virtual ~TownsAudioInterfacePluginDriver() {} - virtual void timerCallback(int timerId) = 0; -}; - -class TownsAudio_PcmChannel; -class TownsAudio_WaveTable; - -class TownsAudioInterface : public TownsPC98_FmSynth { -public: - TownsAudioInterface(Audio::Mixer *mixer, TownsAudioInterfacePluginDriver *driver); - ~TownsAudioInterface(); - - bool init(); - - int callback(int command, ...); - - void setMusicVolume(int volume); - void setSoundEffectVolume(int volume); - // Defines the channels used as sound effect channels for the purpose of ScummVM GUI volume control. - // The first 6 bits are the 6 fm channels. The next 8 bits are pcm channels. - void setSoundEffectChanMask(int mask); - -private: - void nextTickEx(int32 *buffer, uint32 bufferSize); - - void timerCallbackA(); - void timerCallbackB(); - - typedef int (TownsAudioInterface::*TownsAudioIntfCallback)(va_list &); - const TownsAudioIntfCallback *_intfOpcodes; - - int intf_reset(va_list &args); - int intf_keyOn(va_list &args); - int intf_keyOff(va_list &args); - int intf_setPanPos(va_list &args); - int intf_setInstrument(va_list &args); - int intf_loadInstrument(va_list &args); - int intf_setPitch(va_list &args); - int intf_setLevel(va_list &args); - int intf_chanOff(va_list &args); - int intf_writeReg(va_list &args); - int intf_writeRegBuffer(va_list &args); - int intf_readRegBuffer(va_list &args); - int intf_setTimerA(va_list &args); - int intf_setTimerB(va_list &args); - int intf_enableTimerA(va_list &args); - int intf_enableTimerB(va_list &args); - int intf_loadSamples(va_list &args); - int intf_reserveEffectChannels(va_list &args); - int intf_loadWaveTable(va_list &args); - int intf_unloadWaveTable(va_list &args); - int intf_pcmPlayEffect(va_list &args); - int intf_pcmChanOff(va_list &args); - int intf_pcmEffectPlaying(va_list &args); - int intf_fmKeyOn(va_list &args); - int intf_fmKeyOff(va_list &args); - int intf_fmSetPanPos(va_list &args); - int intf_fmSetInstrument(va_list &args); - int intf_fmLoadInstrument(va_list &args); - int intf_fmSetPitch(va_list &args); - int intf_fmSetLevel(va_list &args); - int intf_fmReset(va_list &args); - int intf_setOutputVolume(va_list &args); - int intf_resetOutputVolume(va_list &args); - int intf_updateOutputVolume(va_list &args); - int intf_cdaToggle(va_list &args); - int intf_pcmUpdateEnvelopeGenerator(va_list &args); - - int intf_notImpl(va_list &args); - - void fmReset(); - int fmKeyOn(int chan, int note, int velo); - int fmKeyOff(int chan); - int fmChanOff(int chan); - int fmSetPanPos(int chan, int mode); - int fmSetInstrument(int chan, int instrId); - int fmLoadInstrument(int instrId, const uint8 *data); - int fmSetPitch(int chan, int pitch); - int fmSetLevel(int chan, int lvl); - - void bufferedWriteReg(uint8 part, uint8 regAddress, uint8 value); - - uint8 _fmChanPlaying; - uint8 _fmChanNote[6]; - int16 _fmChanPitch[6]; - - uint8 *_fmSaveReg[2]; - uint8 *_fmInstruments; - - void pcmReset(); - int pcmKeyOn(int chan, int note, int velo); - int pcmKeyOff(int chan); - int pcmChanOff(int chan); - int pcmSetPanPos(int chan, int mode); - int pcmSetInstrument(int chan, int instrId); - int pcmLoadInstrument(int instrId, const uint8 *data); - int pcmSetPitch(int chan, int pitch); - int pcmSetLevel(int chan, int lvl); - void pcmUpdateEnvelopeGenerator(int chan); - - TownsAudio_PcmChannel *_pcmChan; - uint8 _pcmChanOut; - uint8 _pcmChanReserved; - uint8 _pcmChanKeyPressed; - uint8 _pcmChanEffectPlaying; - uint8 _pcmChanKeyPlaying; - - uint8 _pcmChanNote[8]; - uint8 _pcmChanVelo[8]; - uint8 _pcmChanLevel[8]; - - uint8 _numReservedChannels; - uint8 *_pcmInstruments; - - TownsAudio_WaveTable *_waveTables; - uint8 _numWaveTables; - uint32 _waveTablesTotalDataSize; - - void pcmCalcPhaseStep(TownsAudio_PcmChannel *p, TownsAudio_WaveTable *w); - - void updateOutputVolume(); - uint8 _outputVolumeFlags; - uint8 _outputLevel[16]; - uint8 _outputMuteFlags; - - const float _baserate; - uint32 _timerBase; - uint32 _tickLength; - uint32 _timer; - - uint16 _musicVolume; - uint16 _sfxVolume; - int _pcmSfxChanMask; - - TownsAudioInterfacePluginDriver *_drv; - bool _ready; - - static const uint8 _chanFlags[]; - static const uint16 _frequency[]; - static const uint8 _carrier[]; - static const uint8 _fmDefaultInstrument[]; - static const uint16 _pcmPhase1[]; - static const uint16 _pcmPhase2[]; -}; - -#endif - diff --git a/sound/softsynth/fmtowns_pc98/towns_euphony.cpp b/sound/softsynth/fmtowns_pc98/towns_euphony.cpp deleted file mode 100644 index 3a691c407d..0000000000 --- a/sound/softsynth/fmtowns_pc98/towns_euphony.cpp +++ /dev/null @@ -1,905 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/softsynth/fmtowns_pc98/towns_euphony.h" -#include "common/endian.h" - -TownsEuphonyDriver::TownsEuphonyDriver(Audio::Mixer *mixer) : _activeChannels(0), _sustainChannels(0), - _assignedChannels(0), _paraCount(0), _command(0), _tEnable(0), _tMode(0), _tOrdr(0), _tLevel(0), - _tTranspose(0), _musicPos(0), _musicStart(0), _playing(false), _eventBuffer(0), _bufferedEventsCount(0), - _tempoControlMode(0) { - _para[0] = _para[1] = 0; - _intf = new TownsAudioInterface(mixer, this); - resetTempo(); -} - -TownsEuphonyDriver::~TownsEuphonyDriver() { - delete _intf; - delete[] _activeChannels; - delete[] _sustainChannels; - delete[] _assignedChannels; - delete[] _tEnable; - delete[] _tMode; - delete[] _tOrdr; - delete[] _tLevel; - delete[] _tTranspose; -} - -bool TownsEuphonyDriver::init() { - if (!_intf->init()) - return false; - - _activeChannels = new int8[16]; - _sustainChannels = new int8[16]; - _assignedChannels = new ActiveChannel[128]; - _eventBuffer = new DlEvent[64]; - - _tEnable = new uint8[32]; - _tMode = new uint8[32]; - _tOrdr = new uint8[32]; - _tLevel = new int8[32]; - _tTranspose = new int8[32]; - - reset(); - - return true; -} - -void TownsEuphonyDriver::reset() { - _intf->callback(0); - - _intf->callback(74); - _intf->callback(70); - _intf->callback(75, 3); - - setTimerA(true, 1); - setTimerA(false, 1); - setTimerB(true, 221); - - _paraCount = _command = _para[0] = _para[1] = 0; - memset(_sustainChannels, 0, 16); - memset(_activeChannels, -1, 16); - for (int i = 0; i < 128; i++) { - _assignedChannels[i].chan = _assignedChannels[i].next = -1; - _assignedChannels[i].note = _assignedChannels[i].sub = 0; - } - - int e = 0; - for (int i = 0; i < 6; i++) - assignChannel(i, e++); - for (int i = 0x40; i < 0x48; i++) - assignChannel(i, e++); - - resetTables(); - - memset(_eventBuffer, 0, 64 * sizeof(DlEvent)); - _bufferedEventsCount = 0; - - _playing = _endOfTrack = _suspendParsing = _loop = false; - _elapsedEvents = 0; - _tempoDiff = 0; - - resetTempo(); - - if (_tempoControlMode == 1) { - //if (///) - // return; - setTempoIntern(_defaultTempo); - } else { - setTempoIntern(_defaultTempo); - } - - resetControl(); -} - -void TownsEuphonyDriver::loadInstrument(int chanType, int id, const uint8 *data) { - _intf->callback(5, chanType, id, data); -} - -void TownsEuphonyDriver::loadWaveTable(const uint8 *data) { - _intf->callback(34, data); -} - -void TownsEuphonyDriver::unloadWaveTable(int id) { - _intf->callback(35, id); -} - -void TownsEuphonyDriver::reserveSoundEffectChannels(int num) { - _intf->callback(33, num); - uint32 volMask = 0; - - if (num > 8) - return; - - for (uint32 v = 1 << 13; num; num--) { - volMask |= v; - v >>= 1; - } - - _intf->setSoundEffectChanMask(volMask); -} - -int TownsEuphonyDriver::setMusicTempo(int tempo) { - if (tempo > 250) - return 3; - _defaultTempo = tempo; - _trackTempo = tempo; - setTempoIntern(tempo); - return 0; -} - -int TownsEuphonyDriver::startMusicTrack(const uint8 *data, int trackSize, int startTick) { - if (_playing) - return 2; - - _musicPos = _musicStart = data; - _defaultBaseTickLen = _baseTickLen = startTick; - _musicTrackSize = trackSize; - _timeStampBase = _timeStampDest = 0; - _tickCounter = 0; - _playing = true; - - return 0; -} - -void TownsEuphonyDriver::setMusicLoop(bool loop) { - _loop = loop; -} - -void TownsEuphonyDriver::stopParser() { - if (_playing) { - _playing = false; - _pulseCount = 0; - _endOfTrack = false; - flushEventBuffer(); - resetControl(); - } -} - -void TownsEuphonyDriver::continueParsing() { - _suspendParsing = false; -} - -void TownsEuphonyDriver::playSoundEffect(int chan, int note, int velo, const uint8 *data) { - _intf->callback(37, chan, note, velo, data); -} - -void TownsEuphonyDriver::stopSoundEffect(int chan) { - _intf->callback(39, chan); -} - -bool TownsEuphonyDriver::soundEffectIsPlaying(int chan) { - return _intf->callback(40, chan) ? true : false; -} - -void TownsEuphonyDriver::chanPanPos(int chan, int mode) { - _intf->callback(3, chan, mode); -} - -void TownsEuphonyDriver::chanPitch(int chan, int pitch) { - _intf->callback(7, chan, pitch); -} - -void TownsEuphonyDriver::chanVolume(int chan, int vol) { - _intf->callback(8, chan, vol); -} - -void TownsEuphonyDriver::setOutputVolume(int mode, int volLeft, int volRight) { - _intf->callback(67, mode, volLeft, volRight); -} - -int TownsEuphonyDriver::chanEnable(int tableEntry, int val) { - if (tableEntry > 31) - return 3; - _tEnable[tableEntry] = val; - return 0; -} - -int TownsEuphonyDriver::chanMode(int tableEntry, int val) { - if (tableEntry > 31) - return 3; - _tMode[tableEntry] = val; - return 0; -} - -int TownsEuphonyDriver::chanOrdr(int tableEntry, int val) { - if (tableEntry > 31) - return 3; - if (val < 16) - _tOrdr[tableEntry] = val; - return 0; -} - -int TownsEuphonyDriver::chanVolumeShift(int tableEntry, int val) { - if (tableEntry > 31) - return 3; - if (val <= 40) - _tLevel[tableEntry] = (int8)(val & 0xff); - return 0; -} - -int TownsEuphonyDriver::chanNoteShift(int tableEntry, int val) { - if (tableEntry > 31) - return 3; - if (val <= 40) - _tTranspose[tableEntry] = (int8)(val & 0xff); - return 0; -} - -int TownsEuphonyDriver::assignChannel(int chan, int tableEntry) { - if (tableEntry > 15 || chan > 127 || chan < 0) - return 3; - - ActiveChannel *a = &_assignedChannels[chan]; - if (a->chan == tableEntry) - return 0; - - if (a->chan != -1) { - int8 *b = &_activeChannels[a->chan]; - while (*b != chan) { - b = &_assignedChannels[*b].next; - if (*b == -1 && *b != chan) - return 3; - } - - *b = a->next; - - if (a->note) - _intf->callback(2, chan); - - a->chan = a->next = -1; - a->note = 0; - } - - a->next = _activeChannels[tableEntry]; - _activeChannels[tableEntry] = chan; - a->chan = tableEntry; - a->note = a->sub = 0; - - return 0; -} - -void TownsEuphonyDriver::timerCallback(int timerId) { - switch (timerId) { - case 0: - updatePulseCount(); - while (_pulseCount > 0) { - --_pulseCount; - updateTimeStampBase(); - if (!_playing) - continue; - updateEventBuffer(); - updateParser(); - updateCheckEot(); - } - break; - default: - break; - } -} - -void TownsEuphonyDriver::setMusicVolume(int volume) { - _intf->setMusicVolume(volume); -} - -void TownsEuphonyDriver::setSoundEffectVolume(int volume) { - _intf->setSoundEffectVolume(volume); -} - -void TownsEuphonyDriver::resetTables() { - memset(_tEnable, 0xff, 32); - memset(_tMode, 0xff, 16); - memset(_tMode + 16, 0, 16); - for (int i = 0; i < 32; i++) - _tOrdr[i] = i & 0x0f; - memset(_tLevel, 0, 32); - memset(_tTranspose, 0, 32); -} - -void TownsEuphonyDriver::resetTempo() { - _defaultBaseTickLen = _baseTickLen = 0x33; - _pulseCount = 0; - _extraTimingControlRemainder = 0; - _extraTimingControl = 16; - _tempoModifier = 0; - _timeStampDest = 0; - _deltaTicks = 0; - _tickCounter = 0; - _defaultTempo = 90; - _trackTempo = 90; -} - -void TownsEuphonyDriver::setTempoIntern(int tempo) { - tempo = CLIP(tempo + _tempoModifier, 0, 500); - if (_tempoControlMode == 0) { - _timerSetting = 34750 / (tempo + 30); - _extraTimingControl = 16; - - while (_timerSetting < 126) { - _timerSetting <<= 1; - _extraTimingControl <<= 1; - } - - while (_timerSetting > 383) { - _timerSetting >>= 1; - _extraTimingControl >>= 1; - } - - setTimerA(true, -(_timerSetting - 2)); - - } else if (_tempoControlMode == 1) { - _timerSetting = 312500 / (tempo + 30); - _extraTimingControl = 16; - while (_timerSetting < 1105) { - _timerSetting <<= 1; - _extraTimingControl <<= 1; - } - - } else if (_tempoControlMode == 2) { - _timerSetting = 625000 / (tempo + 30); - _extraTimingControlRemainder = 0; - } -} - -void TownsEuphonyDriver::setTimerA(bool enable, int tempo) { - _intf->callback(21, enable ? 255 : 0, tempo); -} - -void TownsEuphonyDriver::setTimerB(bool enable, int tempo) { - _intf->callback(22, enable ? 255 : 0, tempo); -} - -void TownsEuphonyDriver::updatePulseCount() { - int tc = _extraTimingControl + _extraTimingControlRemainder; - _extraTimingControlRemainder = tc & 0x0f; - tc >>= 4; - _tempoDiff -= tc; - - while (_tempoDiff < 0) { - _elapsedEvents++; - _tempoDiff += 4; - } - - if (_playing && !_suspendParsing) - _pulseCount += tc; -} - -void TownsEuphonyDriver::updateTimeStampBase() { - static const uint16 table[] = { 0x180, 0xC0, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18 }; - if ((uint32)(table[_baseTickLen >> 4] * ((_baseTickLen & 0x0f) + 1)) > ++_tickCounter) - return; - ++_timeStampDest; - _tickCounter = 0; - _deltaTicks = 0; -} - -void TownsEuphonyDriver::updateParser() { - for (bool loop = true; loop;) { - uint8 cmd = _musicPos[0]; - - if (cmd == 0xff || cmd == 0xf7) { - jumpNextLoop(); - - } else if (cmd < 0x90) { - _endOfTrack = true; - flushEventBuffer(); - loop = false; - - } else if (_timeStampBase > _timeStampDest) { - loop = false; - - } else { - if (_timeStampBase == _timeStampDest) { - uint16 timeStamp = READ_LE_UINT16(&_musicPos[2]); - uint8 l = (timeStamp & 0xff) + (timeStamp & 0xff); - timeStamp = ((timeStamp & 0xff00) | l) >> 1; - if (timeStamp > _tickCounter) - loop = false; - } - - if (loop) { - if (parseNext()) - loop = false; - } - } - } -} - -void TownsEuphonyDriver::updateCheckEot() { - if (!_endOfTrack || _bufferedEventsCount) - return; - stopParser(); -} - -bool TownsEuphonyDriver::parseNext() { -#define OPC(x) &TownsEuphonyDriver::evt##x - static const EuphonyOpcode opcodes[] = { - OPC(NotImpl), - OPC(SetupNote), - OPC(PolyphonicAftertouch), - OPC(ControlPitch), - OPC(InstrumentChanAftertouch), - OPC(InstrumentChanAftertouch), - OPC(ControlPitch) - }; -#undef OPC - - uint cmd = _musicPos[0]; - if (cmd != 0xfe && cmd != 0xfd) { - if (cmd >= 0xf0) { - cmd &= 0x0f; - if (cmd == 0) - evtLoadInstrument(); - else if (cmd == 2) - evtAdvanceTimestampOffset(); - else if (cmd == 8) - evtTempo(); - else if (cmd == 12) - evtModeOrdrChange(); - jumpNextLoop(); - return false; - - } else if (!(this->*opcodes[(cmd - 0x80) >> 4])()) { - jumpNextLoop(); - return false; - } - } - - if (cmd == 0xfd) { - _suspendParsing = true; - return true; - } - - if (!_loop) { - _endOfTrack = true; - return true; - } - - _endOfTrack = false; - _musicPos = _musicStart; - _timeStampBase = _timeStampDest = _tickCounter = 0; - _baseTickLen = _defaultBaseTickLen; - - return false; -} - -void TownsEuphonyDriver::jumpNextLoop() { - _musicPos += 6; - if (_musicPos >= _musicStart + _musicTrackSize) - _musicPos = _musicStart; -} - -void TownsEuphonyDriver::updateEventBuffer() { - DlEvent *e = _eventBuffer; - for (int i = _bufferedEventsCount; i; e++) { - if (e->evt == 0) - continue; - if (--e->len) { - --i; - continue; - } - processBufferNote(e->mode, e->evt, e->note, e->velo); - e->evt = 0; - --i; - --_bufferedEventsCount; - } -} - -void TownsEuphonyDriver::flushEventBuffer() { - DlEvent *e = _eventBuffer; - for (int i = _bufferedEventsCount; i; e++) { - if (e->evt == 0) - continue; - processBufferNote(e->mode, e->evt, e->note, e->velo); - e->evt = 0; - --i; - --_bufferedEventsCount; - } -} - -void TownsEuphonyDriver::processBufferNote(int mode, int evt, int note, int velo) { - if (!velo) - evt &= 0x8f; - sendEvent(mode, evt); - sendEvent(mode, note); - sendEvent(mode, velo); -} - -void TownsEuphonyDriver::resetControl() { - for (int i = 0; i < 32; i++) { - if (_tOrdr[i] > 15) { - for (int ii = 0; ii < 16; ii++) - resetControlIntern(_tMode[i], ii); - } else { - resetControlIntern(_tMode[i], _tOrdr[i]); - } - } -} - -void TownsEuphonyDriver::resetControlIntern(int mode, int chan) { - sendEvent(mode, 0xb0 | chan); - sendEvent(mode, 0x40); - sendEvent(mode, 0); - sendEvent(mode, 0xb0 | chan); - sendEvent(mode, 0x7b); - sendEvent(mode, 0); - sendEvent(mode, 0xb0 | chan); - sendEvent(mode, 0x79); - sendEvent(mode, 0x40); -} - -uint8 TownsEuphonyDriver::appendEvent(uint8 evt, uint8 chan) { - if (evt >= 0x80 && evt < 0xf0 && _tOrdr[chan] < 16) - return (evt & 0xf0) | _tOrdr[chan]; - return evt; -} - -void TownsEuphonyDriver::sendEvent(uint8 mode, uint8 command) { - if (mode == 0) { - // warning("TownsEuphonyDriver: Mode 0 not implemented"); - - } else if (mode == 0x10) { - warning("TownsEuphonyDriver: Mode 0x10 not implemented"); - - } else if (mode == 0xff) { - if (command >= 0xf0) { - _paraCount = 1; - _command = 0; - } else if (command >= 0x80) { - _paraCount = 1; - _command = command; - } else if (_command >= 0x80) { - switch ((_command - 0x80) >> 4) { - case 0: - if (_paraCount < 2) { - _paraCount++; - _para[0] = command; - } else { - _paraCount = 1; - _para[1] = command; - sendNoteOff(); - } - break; - - case 1: - if (_paraCount < 2) { - _paraCount++; - _para[0] = command; - } else { - _paraCount = 1; - _para[1] = command; - if (command) - sendNoteOn(); - else - sendNoteOff(); - } - break; - - case 2: - if (_paraCount < 2) { - _paraCount++; - _para[0] = command; - } else { - _paraCount = 1; - } - break; - - case 3: - if (_paraCount < 2) { - _paraCount++; - _para[0] = command; - } else { - _paraCount = 1; - _para[1] = command; - - if (_para[0] == 7) - sendChanVolume(); - else if (_para[0] == 10) - sendPanPosition(); - else if (_para[0] == 64) - sendAllNotesOff(); - } - break; - - case 4: - _paraCount = 1; - _para[0] = command; - sendSetInstrument(); - break; - - case 5: - _paraCount = 1; - _para[0] = command; - break; - - case 6: - if (_paraCount < 2) { - _paraCount++; - _para[0] = command; - } else { - _paraCount = 1; - _para[1] = command; - sendPitch(); - } - break; - } - } - } -} - -bool TownsEuphonyDriver::evtSetupNote() { - if (_musicPos[1] > 31) - return false; - if (!_tEnable[_musicPos[1]]) { - jumpNextLoop(); - return (_musicPos[0] == 0xfe || _musicPos[0] == 0xfd) ? true : false; - } - uint8 evt = appendEvent(_musicPos[0], _musicPos[1]); - uint8 mode = _tMode[_musicPos[1]]; - uint8 note = _musicPos[4]; - uint8 velo = _musicPos[5]; - - sendEvent(mode, evt); - sendEvent(mode, applyNoteShift(note)); - sendEvent(mode, applyVolumeShift(velo)); - - jumpNextLoop(); - if (_musicPos[0] == 0xfe || _musicPos[0] == 0xfd) - return true; - - velo = _musicPos[5]; - uint16 len = ((((_musicPos[1] << 4) | (_musicPos[2] << 8)) >> 4) & 0xff) | ((((_musicPos[3] << 4) | (_musicPos[4] << 8)) >> 4) << 8); - - int i = 0; - for (; i < 64; i++) { - if (_eventBuffer[i].evt == 0) - break; - } - - if (i == 64) { - processBufferNote(mode, evt, note, velo); - } else { - _eventBuffer[i].evt = evt; - _eventBuffer[i].mode = mode; - _eventBuffer[i].note = note; - _eventBuffer[i].velo = velo; - _eventBuffer[i].len = len ? len : 1; - _bufferedEventsCount++; - } - - return false; -} - -bool TownsEuphonyDriver::evtPolyphonicAftertouch() { - if (_musicPos[1] > 31) - return false; - if (!_tEnable[_musicPos[1]]) - return false; - - uint8 evt = appendEvent(_musicPos[0], _musicPos[1]); - uint8 mode = _tMode[_musicPos[1]]; - - sendEvent(mode, evt); - sendEvent(mode, applyNoteShift(_musicPos[4])); - sendEvent(mode, _musicPos[5]); - - return false; -} - -bool TownsEuphonyDriver::evtControlPitch() { - if (_musicPos[1] > 31) - return false; - if (!_tEnable[_musicPos[1]]) - return false; - - uint8 evt = appendEvent(_musicPos[0], _musicPos[1]); - uint8 mode = _tMode[_musicPos[1]]; - - sendEvent(mode, evt); - sendEvent(mode, _musicPos[4]); - sendEvent(mode, _musicPos[5]); - - return false; -} - -bool TownsEuphonyDriver::evtInstrumentChanAftertouch() { - if (_musicPos[1] > 31) - return false; - if (!_tEnable[_musicPos[1]]) - return false; - - uint8 evt = appendEvent(_musicPos[0], _musicPos[1]); - uint8 mode = _tMode[_musicPos[1]]; - - sendEvent(mode, evt); - sendEvent(mode, _musicPos[4]); - - return false; -} - -bool TownsEuphonyDriver::evtLoadInstrument() { - return false; -} - -bool TownsEuphonyDriver::evtAdvanceTimestampOffset() { - ++_timeStampBase; - _baseTickLen = _musicPos[1]; - return false; -} - -bool TownsEuphonyDriver::evtTempo() { - uint8 l = _musicPos[4] << 1; - _trackTempo = (l | (_musicPos[5] << 8)) >> 1; - setTempoIntern(_trackTempo); - return false; -} - -bool TownsEuphonyDriver::evtModeOrdrChange() { - if (_musicPos[1] > 31) - return false; - if (!_tEnable[_musicPos[1]]) - return false; - - if (_musicPos[4] == 1) - _tMode[_musicPos[1]] = _musicPos[5]; - else if (_musicPos[4] == 2) - _tOrdr[_musicPos[1]] = _musicPos[5]; - - return false; -} - -uint8 TownsEuphonyDriver::applyNoteShift(uint8 in) { - int out = _tTranspose[_musicPos[1]]; - if (!out) - return in; - out += (in & 0x7f); - - if (out > 127) - out -= 12; - - if (out < 0) - out += 12; - - return out & 0xff; -} - -uint8 TownsEuphonyDriver::applyVolumeShift(uint8 in) { - int out = _tLevel[_musicPos[1]]; - out += (in & 0x7f); - out = CLIP(out, 1, 127); - - return out & 0xff; -} - -void TownsEuphonyDriver::sendNoteOff() { - int8 *chan = &_activeChannels[_command & 0x0f]; - if (*chan == -1) - return; - - while (_assignedChannels[*chan].note != _para[0]) { - chan = &_assignedChannels[*chan].next; - if (*chan == -1) - return; - } - - if (_sustainChannels[_command & 0x0f]) { - _assignedChannels[*chan].note |= 0x80; - } else { - _assignedChannels[*chan].note = 0; - _intf->callback(2, *chan); - } -} - -void TownsEuphonyDriver::sendNoteOn() { - if (!_para[0]) - return; - int8 *chan = &_activeChannels[_command & 0x0f]; - if (*chan == -1) - return; - - do { - _assignedChannels[*chan].sub++; - chan = &_assignedChannels[*chan].next; - } while (*chan != -1); - - chan = &_activeChannels[_command & 0x0f]; - - int d = 0; - int c = 0; - bool found = false; - - do { - if (!_assignedChannels[*chan].note) { - found = true; - break; - } - if (d <= _assignedChannels[*chan].sub) { - c = *chan; - d = _assignedChannels[*chan].sub; - } - chan = &_assignedChannels[*chan].next; - } while (*chan != -1); - - if (found) - c = *chan; - else - _intf->callback(2, c); - - _assignedChannels[c].note = _para[0]; - _assignedChannels[c].sub = 0; - _intf->callback(1, c, _para[0], _para[1]); -} - -void TownsEuphonyDriver::sendChanVolume() { - int8 *chan = &_activeChannels[_command & 0x0f]; - while (*chan != -1) { - _intf->callback(8, *chan, _para[1] & 0x7f); - chan = &_assignedChannels[*chan].next; - } -} - -void TownsEuphonyDriver::sendPanPosition() { - int8 *chan = &_activeChannels[_command & 0x0f]; - while (*chan != -1) { - _intf->callback(3, *chan, _para[1] & 0x7f); - chan = &_assignedChannels[*chan].next; - } -} - -void TownsEuphonyDriver::sendAllNotesOff() { - if (_para[1] > 63) { - _sustainChannels[_command & 0x0f] = -1; - return; - } - - _sustainChannels[_command & 0x0f] = 0; - int8 *chan = &_activeChannels[_command & 0x0f]; - while (*chan != -1) { - if (_assignedChannels[*chan].note & 0x80) { - _assignedChannels[*chan].note = 0; - _intf->callback(2, *chan); - } - chan = &_assignedChannels[*chan].next; - } -} - -void TownsEuphonyDriver::sendSetInstrument() { - int8 *chan = &_activeChannels[_command & 0x0f]; - while (*chan != -1) { - _intf->callback(4, *chan, _para[0]); - _intf->callback(7, *chan, 0); - chan = &_assignedChannels[*chan].next; - } -} - -void TownsEuphonyDriver::sendPitch() { - int8 *chan = &_activeChannels[_command & 0x0f]; - while (*chan != -1) { - _para[0] += _para[0]; - int16 pitch = (((READ_LE_UINT16(_para)) >> 1) & 0x3fff) - 0x2000; - _intf->callback(7, *chan, pitch); - chan = &_assignedChannels[*chan].next; - } -} diff --git a/sound/softsynth/fmtowns_pc98/towns_euphony.h b/sound/softsynth/fmtowns_pc98/towns_euphony.h deleted file mode 100644 index fa1f8ba496..0000000000 --- a/sound/softsynth/fmtowns_pc98/towns_euphony.h +++ /dev/null @@ -1,187 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef TOWNS_EUP_H -#define TOWNS_EUP_H - -#include "sound/softsynth/fmtowns_pc98/towns_audio.h" - -class TownsEuphonyDriver : public TownsAudioInterfacePluginDriver { -public: - TownsEuphonyDriver(Audio::Mixer *mixer); - virtual ~TownsEuphonyDriver(); - - bool init(); - void reset(); - - void loadInstrument(int chanType, int id, const uint8 *data); - void loadWaveTable(const uint8 *data); - void unloadWaveTable(int id); - void reserveSoundEffectChannels(int num); - - int setMusicTempo(int tempo); - int startMusicTrack(const uint8 *data, int trackSize, int startTick); - void setMusicLoop(bool loop); - void stopParser(); - bool parserIsPlaying() {return _playing; } - void continueParsing(); - - void playSoundEffect(int chan, int note, int velo, const uint8 *data); - void stopSoundEffect(int chan); - bool soundEffectIsPlaying(int chan); - - void chanPanPos(int chan, int mode); - void chanPitch(int chan, int pitch); - void chanVolume(int chan, int vol); - - void setOutputVolume(int chanType, int volLeft, int volRight); - - int chanEnable(int tableEntry, int val); - int chanMode(int tableEntry, int val); - int chanOrdr(int tableEntry, int val); - int chanVolumeShift(int tableEntry, int val); - int chanNoteShift(int tableEntry, int val); - - int assignChannel(int chan, int tableEntry); - - void timerCallback(int timerId); - - void setMusicVolume(int volume); - void setSoundEffectVolume(int volume); - - TownsAudioInterface *intf() { - return _intf; - } - -private: - void resetTables(); - - void resetTempo(); - void setTempoIntern(int tempo); - void setTimerA(bool enable, int tempo); - void setTimerB(bool enable, int tempo); - - void updatePulseCount(); - void updateTimeStampBase(); - void updateParser(); - void updateCheckEot(); - - bool parseNext(); - void jumpNextLoop(); - - void updateEventBuffer(); - void flushEventBuffer(); - void processBufferNote(int mode, int evt, int note, int velo); - - void resetControl(); - void resetControlIntern(int mode, int chan); - uint8 appendEvent(uint8 evt, uint8 chan); - - void sendEvent(uint8 mode, uint8 command); - - typedef bool(TownsEuphonyDriver::*EuphonyOpcode)(); - bool evtSetupNote(); - bool evtPolyphonicAftertouch(); - bool evtControlPitch(); - bool evtInstrumentChanAftertouch(); - bool evtLoadInstrument(); - bool evtAdvanceTimestampOffset(); - bool evtTempo(); - bool evtModeOrdrChange(); - bool evtNotImpl() { - return false; - } - - uint8 applyNoteShift(uint8 in); - uint8 applyVolumeShift(uint8 in); - - void sendNoteOff(); - void sendNoteOn(); - void sendChanVolume(); - void sendPanPosition(); - void sendAllNotesOff(); - void sendSetInstrument(); - void sendPitch(); - - int8 *_activeChannels; - int8 *_sustainChannels; - - struct ActiveChannel { - int8 chan; - int8 next; - uint8 note; - uint8 sub; - } *_assignedChannels; - - uint8 *_tEnable; - uint8 *_tMode; - uint8 *_tOrdr; - int8 *_tLevel; - int8 *_tTranspose; - - struct DlEvent { - uint8 evt; - uint8 mode; - uint8 note; - uint8 velo; - uint16 len; - } *_eventBuffer; - int _bufferedEventsCount; - - uint8 _para[2]; - uint8 _paraCount; - uint8 _command; - - uint8 _defaultBaseTickLen; - uint8 _baseTickLen; - uint32 _pulseCount; - int _tempoControlMode; - int _extraTimingControlRemainder; - int _extraTimingControl; - int _timerSetting; - int8 _tempoDiff; - int _tempoModifier; - uint32 _timeStampDest; - uint32 _timeStampBase; - int8 _elapsedEvents; - uint8 _deltaTicks; - uint32 _tickCounter; - uint8 _defaultTempo; - int _trackTempo; - - bool _loop; - bool _playing; - bool _endOfTrack; - bool _suspendParsing; - - const uint8 *_musicStart; - const uint8 *_musicPos; - uint32 _musicTrackSize; - - TownsAudioInterface *_intf; -}; - -#endif - diff --git a/sound/softsynth/fmtowns_pc98/towns_pc98_driver.cpp b/sound/softsynth/fmtowns_pc98/towns_pc98_driver.cpp deleted file mode 100644 index 303f08e6b1..0000000000 --- a/sound/softsynth/fmtowns_pc98/towns_pc98_driver.cpp +++ /dev/null @@ -1,1428 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/softsynth/fmtowns_pc98/towns_pc98_driver.h" -#include "common/endian.h" - -class TownsPC98_MusicChannel { -public: - TownsPC98_MusicChannel(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, - uint8 key, uint8 prt, uint8 id); - virtual ~TownsPC98_MusicChannel(); - virtual void init(); - - typedef enum channelState { - CHS_RECALCFREQ = 0x01, - CHS_KEYOFF = 0x02, - CHS_SSGOFF = 0x04, - CHS_VBROFF = 0x08, - CHS_ALLOFF = 0x0f, - CHS_PROTECT = 0x40, - CHS_EOT = 0x80 - } ChannelState; - - virtual void loadData(uint8 *data); - virtual void processEvents(); - virtual void processFrequency(); - virtual bool processControlEvent(uint8 cmd); - - virtual void keyOn(); - void keyOff(); - - void setOutputLevel(); - virtual void fadeStep(); - virtual void reset(); - - const uint8 _idFlag; - -protected: - void setupVibrato(); - bool processVibrato(); - - bool control_dummy(uint8 para); - bool control_f0_setPatch(uint8 para); - bool control_f1_presetOutputLevel(uint8 para); - bool control_f2_setKeyOffTime(uint8 para); - bool control_f3_setFreqLSB(uint8 para); - bool control_f4_setOutputLevel(uint8 para); - bool control_f5_setTempo(uint8 para); - bool control_f6_repeatSection(uint8 para); - bool control_f7_setupVibrato(uint8 para); - bool control_f8_toggleVibrato(uint8 para); - bool control_fa_writeReg(uint8 para); - virtual bool control_fb_incOutLevel(uint8 para); - virtual bool control_fc_decOutLevel(uint8 para); - bool control_fd_jump(uint8 para); - virtual bool control_ff_endOfTrack(uint8 para); - - uint8 _ticksLeft; - uint8 _algorithm; - uint8 _instr; - uint8 _totalLevel; - uint8 _frqBlockMSB; - int8 _frqLSB; - uint8 _keyOffTime; - bool _hold; - uint8 *_dataPtr; - uint8 _vbrInitDelayHi; - uint8 _vbrInitDelayLo; - int16 _vbrModInitVal; - uint8 _vbrDuration; - uint8 _vbrCurDelay; - int16 _vbrModCurVal; - uint8 _vbrDurLeft; - uint16 _frequency; - uint8 _block; - uint8 _regOffset; - uint8 _flags; - uint8 _ssgTl; - uint8 _ssgStep; - uint8 _ssgTicksLeft; - uint8 _ssgTargetLvl; - uint8 _ssgStartLvl; - - const uint8 _chanNum; - const uint8 _keyNum; - const uint8 _part; - - TownsPC98_AudioDriver *_drv; - - typedef bool (TownsPC98_MusicChannel::*ControlEventFunc)(uint8 para); - const ControlEventFunc *controlEvents; -}; - -class TownsPC98_MusicChannelSSG : public TownsPC98_MusicChannel { -public: - TownsPC98_MusicChannelSSG(TownsPC98_AudioDriver *driver, uint8 regOffs, - uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id); - virtual ~TownsPC98_MusicChannelSSG() {} - void init(); - - virtual void loadData(uint8 *data); - void processEvents(); - void processFrequency(); - bool processControlEvent(uint8 cmd); - - void keyOn(); - void nextShape(); - - void protect(); - void restore(); - virtual void reset(); - - void fadeStep(); - -protected: - void setOutputLevel(uint8 lvl); - - bool control_f0_setPatch(uint8 para); - bool control_f1_setTotalLevel(uint8 para); - bool control_f4_setAlgorithm(uint8 para); - bool control_f9_loadCustomPatch(uint8 para); - bool control_fb_incOutLevel(uint8 para); - bool control_fc_decOutLevel(uint8 para); - bool control_ff_endOfTrack(uint8 para); - - typedef bool (TownsPC98_MusicChannelSSG::*ControlEventFunc)(uint8 para); - const ControlEventFunc *controlEvents; -}; - -class TownsPC98_SfxChannel : public TownsPC98_MusicChannelSSG { -public: - TownsPC98_SfxChannel(TownsPC98_AudioDriver *driver, uint8 regOffs, - uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) : - TownsPC98_MusicChannelSSG(driver, regOffs, flgs, num, key, prt, id) {} - ~TownsPC98_SfxChannel() {} - - void loadData(uint8 *data); - void reset(); -}; - -#ifndef DISABLE_PC98_RHYTHM_CHANNEL -class TownsPC98_MusicChannelPCM : public TownsPC98_MusicChannel { -public: - TownsPC98_MusicChannelPCM(TownsPC98_AudioDriver *driver, uint8 regOffs, - uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id); - ~TownsPC98_MusicChannelPCM() {} - void init(); - - void loadData(uint8 *data); - void processEvents(); - bool processControlEvent(uint8 cmd); - -private: - bool control_f1_prcStart(uint8 para); - bool control_ff_endOfTrack(uint8 para); - - typedef bool (TownsPC98_MusicChannelPCM::*ControlEventFunc)(uint8 para); - const ControlEventFunc *controlEvents; -}; -#endif - -TownsPC98_MusicChannel::TownsPC98_MusicChannel(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, - uint8 key, uint8 prt, uint8 id) : _drv(driver), _regOffset(regOffs), _flags(flgs), _chanNum(num), _keyNum(key), - _part(prt), _idFlag(id), controlEvents(0) { - - _ticksLeft = _algorithm = _instr = _totalLevel = _frqBlockMSB = _keyOffTime = 0; - _ssgStartLvl = _ssgTl = _ssgStep = _ssgTicksLeft = _ssgTargetLvl = _block = 0; - _vbrInitDelayHi = _vbrInitDelayLo = _vbrDuration = _vbrCurDelay = _vbrDurLeft = 0; - _frqLSB = 0; - _hold = false; - _dataPtr = 0; - _vbrModInitVal = _vbrModCurVal = 0; - _frequency = 0; -} - -TownsPC98_MusicChannel::~TownsPC98_MusicChannel() { -} - -void TownsPC98_MusicChannel::init() { -#define Control(x) &TownsPC98_MusicChannel::control_##x - static const ControlEventFunc ctrlEvents[] = { - Control(f0_setPatch), - Control(f1_presetOutputLevel), - Control(f2_setKeyOffTime), - Control(f3_setFreqLSB), - Control(f4_setOutputLevel), - Control(f5_setTempo), - Control(f6_repeatSection), - Control(f7_setupVibrato), - Control(f8_toggleVibrato), - Control(dummy), - Control(fa_writeReg), - Control(fb_incOutLevel), - Control(fc_decOutLevel), - Control(fd_jump), - Control(dummy), - Control(ff_endOfTrack) - }; -#undef Control - - controlEvents = ctrlEvents; -} - -void TownsPC98_MusicChannel::keyOff() { - // all operators off - uint8 value = _keyNum & 0x0f; - if (_part) - value |= 4; - uint8 regAddress = 0x28; - _drv->writeReg(0, regAddress, value); - _flags |= CHS_KEYOFF; -} - -void TownsPC98_MusicChannel::keyOn() { - // all operators on - uint8 value = _keyNum | 0xf0; - if (_part) - value |= 4; - uint8 regAddress = 0x28; - _drv->writeReg(0, regAddress, value); -} - -void TownsPC98_MusicChannel::loadData(uint8 *data) { - _flags = (_flags & ~CHS_EOT) | CHS_ALLOFF; - _ticksLeft = 1; - _dataPtr = data; - _totalLevel = 0x7F; - - uint8 *tmp = _dataPtr; - for (bool loop = true; loop;) { - uint8 cmd = *tmp++; - if (cmd < 0xf0) { - tmp++; - } else if (cmd == 0xff) { - if (READ_LE_UINT16(tmp)) { - _drv->_looping |= _idFlag; - tmp += _drv->_opnFxCmdLen[cmd - 240]; - } else - loop = false; - } else if (cmd == 0xf6) { - // reset repeat section countdown - tmp[0] = tmp[1]; - tmp += 4; - } else { - tmp += _drv->_opnFxCmdLen[cmd - 240]; - } - } -} - -void TownsPC98_MusicChannel::processEvents() { - if (_flags & CHS_EOT) - return; - - if (!_hold && _ticksLeft == _keyOffTime) - keyOff(); - - if (--_ticksLeft) - return; - - if (!_hold) - keyOff(); - - uint8 cmd = 0; - bool loop = true; - - while (loop) { - cmd = *_dataPtr++; - if (cmd < 0xf0) - loop = false; - else if (!processControlEvent(cmd)) - return; - } - - uint8 para = *_dataPtr++; - - if (cmd == 0x80) { - keyOff(); - _hold = false; - } else { - keyOn(); - - if (_hold == false || cmd != _frqBlockMSB) - _flags |= CHS_RECALCFREQ; - - _hold = (para & 0x80) ? true : false; - _frqBlockMSB = cmd; - } - - _ticksLeft = para & 0x7f; -} - -void TownsPC98_MusicChannel::processFrequency() { - if (_flags & CHS_RECALCFREQ) { - - _frequency = (READ_LE_UINT16(&_drv->_opnFreqTable[(_frqBlockMSB & 0x0f) << 1]) + _frqLSB) | (((_frqBlockMSB & 0x70) >> 1) << 8); - - _drv->writeReg(_part, _regOffset + 0xa4, (_frequency >> 8)); - _drv->writeReg(_part, _regOffset + 0xa0, (_frequency & 0xff)); - - setupVibrato(); - } - - if (!(_flags & CHS_VBROFF)) { - if (!processVibrato()) - return; - - _drv->writeReg(_part, _regOffset + 0xa4, (_frequency >> 8)); - _drv->writeReg(_part, _regOffset + 0xa0, (_frequency & 0xff)); - } -} - -void TownsPC98_MusicChannel::setupVibrato() { - _vbrCurDelay = _vbrInitDelayHi; - if (_flags & CHS_KEYOFF) { - _vbrModCurVal = _vbrModInitVal; - _vbrCurDelay += _vbrInitDelayLo; - } - _vbrDurLeft = (_vbrDuration >> 1); - _flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ); -} - -bool TownsPC98_MusicChannel::processVibrato() { - if (--_vbrCurDelay) - return false; - - _vbrCurDelay = _vbrInitDelayHi; - _frequency += _vbrModCurVal; - - if (!--_vbrDurLeft) { - _vbrDurLeft = _vbrDuration; - _vbrModCurVal = -_vbrModCurVal; - } - - return true; -} - -bool TownsPC98_MusicChannel::processControlEvent(uint8 cmd) { - uint8 para = *_dataPtr++; - return (this->*controlEvents[cmd & 0x0f])(para); -} - -void TownsPC98_MusicChannel::setOutputLevel() { - uint8 outopr = _drv->_opnCarrier[_algorithm]; - uint8 reg = 0x40 + _regOffset; - - for (int i = 0; i < 4; i++) { - if (outopr & 1) - _drv->writeReg(_part, reg, _totalLevel); - outopr >>= 1; - reg += 4; - } -} - -void TownsPC98_MusicChannel::fadeStep() { - _totalLevel += 3; - if (_totalLevel > 0x7f) - _totalLevel = 0x7f; - setOutputLevel(); -} - -void TownsPC98_MusicChannel::reset() { - _hold = false; - _keyOffTime = 0; - _ticksLeft = 1; - - _flags = (_flags & ~CHS_EOT) | CHS_ALLOFF; - - _totalLevel = 0; - _algorithm = 0; - _flags = CHS_EOT; - _algorithm = 0; - - _block = 0; - _frequency = 0; - _frqBlockMSB = 0; - _frqLSB = 0; - - _ssgTl = 0; - _ssgStartLvl = 0; - _ssgTargetLvl = 0; - _ssgStep = 0; - _ssgTicksLeft = 0; - - _vbrInitDelayHi = 0; - _vbrInitDelayLo = 0; - _vbrModInitVal = 0; - _vbrDuration = 0; - _vbrCurDelay = 0; - _vbrModCurVal = 0; - _vbrDurLeft = 0; -} - -bool TownsPC98_MusicChannel::control_f0_setPatch(uint8 para) { - _instr = para; - uint8 reg = _regOffset + 0x80; - - for (int i = 0; i < 4; i++) { - // set release rate for each operator - _drv->writeReg(_part, reg, 0x0f); - reg += 4; - } - - const uint8 *tptr = _drv->_patches + ((uint32)_instr << 5); - reg = _regOffset + 0x30; - - // write registers 0x30 to 0x8f - for (int i = 0; i < 6; i++) { - _drv->writeReg(_part, reg, tptr[0]); - reg += 4; - _drv->writeReg(_part, reg, tptr[2]); - reg += 4; - _drv->writeReg(_part, reg, tptr[1]); - reg += 4; - _drv->writeReg(_part, reg, tptr[3]); - reg += 4; - tptr += 4; - } - - reg = _regOffset + 0xB0; - _algorithm = tptr[0] & 7; - // set feedback and algorithm - _drv->writeReg(_part, reg, tptr[0]); - - setOutputLevel(); - return true; -} - -bool TownsPC98_MusicChannel::control_f1_presetOutputLevel(uint8 para) { - if (_drv->_fading) - return true; - - _totalLevel = _drv->_opnLvlPresets[para]; - setOutputLevel(); - return true; -} - -bool TownsPC98_MusicChannel::control_f2_setKeyOffTime(uint8 para) { - _keyOffTime = para; - return true; -} - -bool TownsPC98_MusicChannel::control_f3_setFreqLSB(uint8 para) { - _frqLSB = (int8) para; - return true; -} - -bool TownsPC98_MusicChannel::control_f4_setOutputLevel(uint8 para) { - if (_drv->_fading) - return true; - - _totalLevel = para; - setOutputLevel(); - return true; -} - -bool TownsPC98_MusicChannel::control_f5_setTempo(uint8 para) { - _drv->setMusicTempo(para); - return true; -} - -bool TownsPC98_MusicChannel::control_f6_repeatSection(uint8 para) { - _dataPtr--; - _dataPtr[0]--; - - if (*_dataPtr) { - // repeat section until counter has reached zero - _dataPtr = _drv->_trackPtr + READ_LE_UINT16(_dataPtr + 2); - } else { - // reset counter, advance to next section - _dataPtr[0] = _dataPtr[1]; - _dataPtr += 4; - } - return true; -} - -bool TownsPC98_MusicChannel::control_f7_setupVibrato(uint8 para) { - _vbrInitDelayHi = _dataPtr[0]; - _vbrInitDelayLo = para; - _vbrModInitVal = (int16) READ_LE_UINT16(_dataPtr + 1); - _vbrDuration = _dataPtr[3]; - _dataPtr += 4; - _flags = (_flags & ~CHS_VBROFF) | CHS_KEYOFF | CHS_RECALCFREQ; - return true; -} - -bool TownsPC98_MusicChannel::control_f8_toggleVibrato(uint8 para) { - if (para == 0x10) { - if (*_dataPtr++) { - _flags = (_flags & ~CHS_VBROFF) | CHS_KEYOFF; - } else { - _flags |= CHS_VBROFF; - } - } else { - /* NOT IMPLEMENTED - uint8 skipChannels = para / 36; - uint8 entry = para % 36; - TownsPC98_AudioDriver::TownsPC98_MusicChannel *t = &chan[skipChannels]; - - t->unnamedEntries[entry] = *_dataPtr++;*/ - } - return true; -} - -bool TownsPC98_MusicChannel::control_fa_writeReg(uint8 para) { - _drv->writeReg(_part, para, *_dataPtr++); - return true; -} - -bool TownsPC98_MusicChannel::control_fb_incOutLevel(uint8 para) { - _dataPtr--; - if (_drv->_fading) - return true; - - uint8 val = (_totalLevel + 3); - if (val > 0x7f) - val = 0x7f; - - _totalLevel = val; - setOutputLevel(); - return true; -} - -bool TownsPC98_MusicChannel::control_fc_decOutLevel(uint8 para) { - _dataPtr--; - if (_drv->_fading) - return true; - - int8 val = (int8)(_totalLevel - 3); - if (val < 0) - val = 0; - - _totalLevel = (uint8) val; - setOutputLevel(); - return true; -} - -bool TownsPC98_MusicChannel::control_fd_jump(uint8 para) { - uint8 *tmp = _drv->_trackPtr + READ_LE_UINT16(_dataPtr - 1); - _dataPtr = (tmp[1] == 1) ? tmp : (_dataPtr + 1); - return true; -} - -bool TownsPC98_MusicChannel::control_dummy(uint8 para) { - _dataPtr--; - return true; -} - -bool TownsPC98_MusicChannel::control_ff_endOfTrack(uint8 para) { - uint16 val = READ_LE_UINT16(--_dataPtr); - if (val) { - // loop - _dataPtr = _drv->_trackPtr + val; - return true; - } else { - // quit parsing for active channel - --_dataPtr; - _flags |= CHS_EOT; - _drv->_finishedChannelsFlag |= _idFlag; - keyOff(); - return false; - } -} - -TownsPC98_MusicChannelSSG::TownsPC98_MusicChannelSSG(TownsPC98_AudioDriver *driver, uint8 regOffs, - uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) : - TownsPC98_MusicChannel(driver, regOffs, flgs, num, key, prt, id), controlEvents(0) { -} - -void TownsPC98_MusicChannelSSG::init() { - _algorithm = 0x80; - -#define Control(x) &TownsPC98_MusicChannelSSG::control_##x - static const ControlEventFunc ctrlEventsSSG[] = { - Control(f0_setPatch), - Control(f1_setTotalLevel), - Control(f2_setKeyOffTime), - Control(f3_setFreqLSB), - Control(f4_setAlgorithm), - Control(f5_setTempo), - Control(f6_repeatSection), - Control(f7_setupVibrato), - Control(f8_toggleVibrato), - Control(f9_loadCustomPatch), - Control(fa_writeReg), - Control(fb_incOutLevel), - Control(fc_decOutLevel), - Control(fd_jump), - Control(dummy), - Control(ff_endOfTrack) - }; -#undef Control - - controlEvents = ctrlEventsSSG; -} - -void TownsPC98_MusicChannelSSG::processEvents() { - if (_flags & CHS_EOT) - return; - - _drv->toggleRegProtection(_flags & CHS_PROTECT ? true : false); - - if (!_hold && _ticksLeft == _keyOffTime) - nextShape(); - - if (!--_ticksLeft) { - - uint8 cmd = 0; - bool loop = true; - - while (loop) { - cmd = *_dataPtr++; - if (cmd < 0xf0) - loop = false; - else if (!processControlEvent(cmd)) - return; - } - - uint8 para = *_dataPtr++; - - if (cmd == 0x80) { - nextShape(); - _hold = false; - } else { - if (!_hold) { - _instr &= 0xf0; - _ssgStep = _drv->_ssgPatches[_instr]; - _ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f; - _ssgTargetLvl = _drv->_ssgPatches[_instr + 2]; - _ssgStartLvl = _drv->_ssgPatches[_instr + 3]; - _flags = (_flags & ~CHS_SSGOFF) | CHS_KEYOFF; - } - - keyOn(); - - if (_hold == false || cmd != _frqBlockMSB) - _flags |= CHS_RECALCFREQ; - - _hold = (para & 0x80) ? true : false; - _frqBlockMSB = cmd; - } - - _ticksLeft = para & 0x7f; - } - - if (!(_flags & CHS_SSGOFF)) { - if (--_ssgTicksLeft) { - if (!_drv->_fading) - setOutputLevel(_ssgStartLvl); - return; - } - - _ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f; - - if (_drv->_ssgPatches[_instr + 1] & 0x80) { - uint8 t = _ssgStartLvl - _ssgStep; - - if (_ssgStep <= _ssgStartLvl && _ssgTargetLvl < t) { - if (!_drv->_fading) - setOutputLevel(t); - return; - } - } else { - int t = _ssgStartLvl + _ssgStep; - uint8 p = (uint8)(t & 0xff); - - if (t < 256 && _ssgTargetLvl > p) { - if (!_drv->_fading) - setOutputLevel(p); - return; - } - } - - setOutputLevel(_ssgTargetLvl); - if (_ssgStartLvl && !(_instr & 8)) { - _instr += 4; - _ssgStep = _drv->_ssgPatches[_instr]; - _ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f; - _ssgTargetLvl = _drv->_ssgPatches[_instr + 2]; - } else { - _flags |= CHS_SSGOFF; - setOutputLevel(0); - } - } -} - -void TownsPC98_MusicChannelSSG::processFrequency() { - if (_algorithm & 0x40) - return; - - if (_flags & CHS_RECALCFREQ) { - _block = _frqBlockMSB >> 4; - _frequency = READ_LE_UINT16(&_drv->_opnFreqTableSSG[(_frqBlockMSB & 0x0f) << 1]) + _frqLSB; - - uint16 f = _frequency >> _block; - _drv->writeReg(_part, _regOffset << 1, f & 0xff); - _drv->writeReg(_part, (_regOffset << 1) + 1, f >> 8); - - setupVibrato(); - } - - if (!(_flags & (CHS_EOT | CHS_VBROFF | CHS_SSGOFF))) { - if (!processVibrato()) - return; - - uint16 f = _frequency >> _block; - _drv->writeReg(_part, _regOffset << 1, f & 0xff); - _drv->writeReg(_part, (_regOffset << 1) + 1, f >> 8); - } -} - -bool TownsPC98_MusicChannelSSG::processControlEvent(uint8 cmd) { - uint8 para = *_dataPtr++; - return (this->*controlEvents[cmd & 0x0f])(para); -} - -void TownsPC98_MusicChannelSSG::nextShape() { - _instr = (_instr & 0xf0) + 0x0c; - _ssgStep = _drv->_ssgPatches[_instr]; - _ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f; - _ssgTargetLvl = _drv->_ssgPatches[_instr + 2]; -} - -void TownsPC98_MusicChannelSSG::keyOn() { - uint8 c = 0x7b; - uint8 t = (_algorithm & 0xC0) << 1; - if (_algorithm & 0x80) - t |= 4; - - c = (c << (_regOffset + 1)) | (c >> (7 - _regOffset)); - t = (t << (_regOffset + 1)) | (t >> (7 - _regOffset)); - - if (!(_algorithm & 0x80)) - _drv->writeReg(_part, 6, _algorithm & 0x7f); - - uint8 e = (_drv->readSSGStatus() & c) | t; - _drv->writeReg(_part, 7, e); -} - -void TownsPC98_MusicChannelSSG::protect() { - _flags |= CHS_PROTECT; -} - -void TownsPC98_MusicChannelSSG::restore() { - _flags &= ~CHS_PROTECT; - keyOn(); - _drv->writeReg(_part, 8 + _regOffset, _ssgTl); - uint16 f = _frequency >> _block; - _drv->writeReg(_part, _regOffset << 1, f & 0xff); - _drv->writeReg(_part, (_regOffset << 1) + 1, f >> 8); -} - -void TownsPC98_MusicChannelSSG::loadData(uint8 *data) { - _drv->toggleRegProtection(_flags & CHS_PROTECT ? true : false); - TownsPC98_MusicChannel::loadData(data); - setOutputLevel(0); - _algorithm = 0x80; -} - -void TownsPC98_MusicChannelSSG::setOutputLevel(uint8 lvl) { - _ssgStartLvl = lvl; - uint16 newTl = (((uint16)_totalLevel + 1) * (uint16)lvl) >> 8; - if (newTl == _ssgTl) - return; - _ssgTl = newTl; - _drv->writeReg(_part, 8 + _regOffset, _ssgTl); -} - -void TownsPC98_MusicChannelSSG::reset() { - TownsPC98_MusicChannel::reset(); - - // Unlike the original we restore the default patch data. This fixes a bug - // where certain sound effects would bring each other out of tune (e.g. the - // dragon's fire in Darm's house in Kyra 1 would sound different each time - // you triggered another sfx by dropping an item etc.) - uint8 i = (10 + _regOffset) << 4; - const uint8 *src = &_drv->_drvTables[156]; - _drv->_ssgPatches[i] = src[i]; - _drv->_ssgPatches[i + 3] = src[i + 3]; - _drv->_ssgPatches[i + 4] = src[i + 4]; - _drv->_ssgPatches[i + 6] = src[i + 6]; - _drv->_ssgPatches[i + 8] = src[i + 8]; - _drv->_ssgPatches[i + 12] = src[i + 12]; -} - -void TownsPC98_MusicChannelSSG::fadeStep() { - _totalLevel--; - if ((int8)_totalLevel < 0) - _totalLevel = 0; - setOutputLevel(_ssgStartLvl); -} - -bool TownsPC98_MusicChannelSSG::control_f0_setPatch(uint8 para) { - _instr = para << 4; - para = (para >> 3) & 0x1e; - if (para) - return control_f4_setAlgorithm(para | 0x40); - return true; -} - -bool TownsPC98_MusicChannelSSG::control_f1_setTotalLevel(uint8 para) { - if (!_drv->_fading) - _totalLevel = para; - return true; -} - -bool TownsPC98_MusicChannelSSG::control_f4_setAlgorithm(uint8 para) { - _algorithm = para; - return true; -} - -bool TownsPC98_MusicChannelSSG::control_f9_loadCustomPatch(uint8 para) { - _instr = (_drv->_sfxOffs + 10 + _regOffset) << 4; - _drv->_ssgPatches[_instr] = *_dataPtr++; - _drv->_ssgPatches[_instr + 3] = para; - _drv->_ssgPatches[_instr + 4] = *_dataPtr++; - _drv->_ssgPatches[_instr + 6] = *_dataPtr++; - _drv->_ssgPatches[_instr + 8] = *_dataPtr++; - _drv->_ssgPatches[_instr + 12] = *_dataPtr++; - return true; -} - -bool TownsPC98_MusicChannelSSG::control_fb_incOutLevel(uint8 para) { - _dataPtr--; - if (_drv->_fading) - return true; - - _totalLevel--; - if ((int8)_totalLevel < 0) - _totalLevel = 0; - - return true; -} - -bool TownsPC98_MusicChannelSSG::control_fc_decOutLevel(uint8 para) { - _dataPtr--; - if (_drv->_fading) - return true; - - if (_totalLevel + 1 < 0x10) - _totalLevel++; - - return true; -} - -bool TownsPC98_MusicChannelSSG::control_ff_endOfTrack(uint8 para) { - if (!_drv->_sfxOffs) { - uint16 val = READ_LE_UINT16(--_dataPtr); - if (val) { - // loop - _dataPtr = _drv->_trackPtr + val; - return true; - } else { - // stop parsing - if (!_drv->_fading) - setOutputLevel(0); - --_dataPtr; - _flags |= CHS_EOT; - _drv->_finishedSSGFlag |= _idFlag; - } - } else { - // end of sfx track - restore ssg music channel - _flags |= CHS_EOT; - _drv->_finishedSfxFlag |= _idFlag; - _drv->_ssgChannels[_chanNum]->restore(); - } - - return false; -} - -void TownsPC98_SfxChannel::loadData(uint8 *data) { - _flags = CHS_ALLOFF; - _ticksLeft = 1; - _dataPtr = data; - _ssgTl = 0xff; - _algorithm = 0x80; - - uint8 *tmp = _dataPtr; - for (bool loop = true; loop;) { - uint8 cmd = *tmp++; - if (cmd < 0xf0) { - tmp++; - } else if (cmd == 0xff) { - loop = false; - } else if (cmd == 0xf6) { - // reset repeat section countdown - tmp[0] = tmp[1]; - tmp += 4; - } else { - tmp += _drv->_opnFxCmdLen[cmd - 240]; - } - } -} - -void TownsPC98_SfxChannel::reset() { - TownsPC98_MusicChannel::reset(); - - // Unlike the original we restore the default patch data. This fixes a bug - // where certain sound effects would bring each other out of tune (e.g. the - // dragon's fire in Darm's house in Kyra 1 would sound different each time - // you triggered another sfx by dropping an item etc.) - uint8 i = (13 + _regOffset) << 4; - const uint8 *src = &_drv->_drvTables[156]; - _drv->_ssgPatches[i] = src[i]; - _drv->_ssgPatches[i + 3] = src[i + 3]; - _drv->_ssgPatches[i + 4] = src[i + 4]; - _drv->_ssgPatches[i + 6] = src[i + 6]; - _drv->_ssgPatches[i + 8] = src[i + 8]; - _drv->_ssgPatches[i + 12] = src[i + 12]; -} - -#ifndef DISABLE_PC98_RHYTHM_CHANNEL -TownsPC98_MusicChannelPCM::TownsPC98_MusicChannelPCM(TownsPC98_AudioDriver *driver, uint8 regOffs, - uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) : - TownsPC98_MusicChannel(driver, regOffs, flgs, num, key, prt, id), controlEvents(0) { -} - -void TownsPC98_MusicChannelPCM::init() { - _algorithm = 0x80; - -#define Control(x) &TownsPC98_MusicChannelPCM::control_##x - static const ControlEventFunc ctrlEventsPCM[] = { - Control(dummy), - Control(f1_prcStart), - Control(dummy), - Control(dummy), - Control(dummy), - Control(dummy), - Control(f6_repeatSection), - Control(dummy), - Control(dummy), - Control(dummy), - Control(fa_writeReg), - Control(dummy), - Control(dummy), - Control(dummy), - Control(dummy), - Control(ff_endOfTrack) - }; -#undef Control - - controlEvents = ctrlEventsPCM; -} - -void TownsPC98_MusicChannelPCM::loadData(uint8 *data) { - _flags = (_flags & ~CHS_EOT) | CHS_ALLOFF; - _ticksLeft = 1; - _dataPtr = data; - _totalLevel = 0x7F; -} - -void TownsPC98_MusicChannelPCM::processEvents() { - if (_flags & CHS_EOT) - return; - - if (--_ticksLeft) - return; - - uint8 cmd = 0; - bool loop = true; - - while (loop) { - cmd = *_dataPtr++; - if (cmd == 0x80) { - loop = false; - } else if (cmd < 0xf0) { - _drv->writeReg(_part, 0x10, cmd); - } else if (!processControlEvent(cmd)) { - return; - } - } - - _ticksLeft = *_dataPtr++; -} - -bool TownsPC98_MusicChannelPCM::processControlEvent(uint8 cmd) { - uint8 para = *_dataPtr++; - return (this->*controlEvents[cmd & 0x0f])(para); -} - -bool TownsPC98_MusicChannelPCM::control_f1_prcStart(uint8 para) { - _totalLevel = para; - _drv->writeReg(_part, 0x11, para); - return true; -} - -bool TownsPC98_MusicChannelPCM::control_ff_endOfTrack(uint8 para) { - uint16 val = READ_LE_UINT16(--_dataPtr); - if (val) { - // loop - _dataPtr = _drv->_trackPtr + val; - return true; - } else { - // quit parsing for active channel - --_dataPtr; - _flags |= CHS_EOT; - _drv->_finishedRhythmFlag |= _idFlag; - return false; - } -} -#endif // DISABLE_PC98_RHYTHM_CHANNEL - -TownsPC98_AudioDriver::TownsPC98_AudioDriver(Audio::Mixer *mixer, EmuType type) : TownsPC98_FmSynth(mixer, type), - _channels(0), _ssgChannels(0), _sfxChannels(0), -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - _rhythmChannel(0), -#endif - _trackPtr(0), _sfxData(0), _sfxOffs(0), _ssgPatches(0), - _patches(0), _sfxBuffer(0), _musicBuffer(0), - - _opnCarrier(_drvTables + 76), _opnFreqTable(_drvTables + 108), _opnFreqTableSSG(_drvTables + 132), - _opnFxCmdLen(_drvTables + 36), _opnLvlPresets(_drvTables + (type == kTypeTowns ? 52 : 84)), - - _updateChannelsFlag(type == kType26 ? 0x07 : 0x3F), _finishedChannelsFlag(0), - _updateSSGFlag(type == kTypeTowns ? 0x00 : 0x07), _finishedSSGFlag(0), - _updateRhythmFlag(type == kType86 ? -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - 0x01 -#else - 0x00 -#endif - : 0x00), _finishedRhythmFlag(0), - _updateSfxFlag(0), _finishedSfxFlag(0), - - _musicTickCounter(0), - - _musicVolume(255), _sfxVolume(255), - - _musicPlaying(false), _sfxPlaying(false), _fading(false), _looping(0), _ready(false) { - - _sfxOffsets[0] = _sfxOffsets[1] = 0; -} - -TownsPC98_AudioDriver::~TownsPC98_AudioDriver() { - _ready = false; - deinit(); - - if (_channels) { - for (int i = 0; i < _numChan; i++) - delete _channels[i]; - delete[] _channels; - } - - if (_ssgChannels) { - for (int i = 0; i < _numSSG; i++) - delete _ssgChannels[i]; - delete[] _ssgChannels; - } - - if (_sfxChannels) { - for (int i = 0; i < 2; i++) - delete _sfxChannels[i]; - delete[] _sfxChannels; - } -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - delete _rhythmChannel; -#endif - - delete[] _ssgPatches; -} - -bool TownsPC98_AudioDriver::init() { - if (_ready) { - reset(); - return true; - } - - TownsPC98_FmSynth::init(); - - setVolumeChannelMasks(-1, 0); - - _channels = new TownsPC98_MusicChannel *[_numChan]; - for (int i = 0; i < _numChan; i++) { - int ii = i * 6; - _channels[i] = new TownsPC98_MusicChannel(this, _drvTables[ii], _drvTables[ii + 1], - _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]); - _channels[i]->init(); - } - - if (_numSSG) { - _ssgPatches = new uint8[256]; - memcpy(_ssgPatches, _drvTables + 156, 256); - - _ssgChannels = new TownsPC98_MusicChannelSSG *[_numSSG]; - for (int i = 0; i < _numSSG; i++) { - int ii = i * 6; - _ssgChannels[i] = new TownsPC98_MusicChannelSSG(this, _drvTables[ii], _drvTables[ii + 1], - _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]); - _ssgChannels[i]->init(); - } - - _sfxChannels = new TownsPC98_SfxChannel *[2]; - for (int i = 0; i < 2; i++) { - int ii = (i + 1) * 6; - _sfxChannels[i] = new TownsPC98_SfxChannel(this, _drvTables[ii], _drvTables[ii + 1], - _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]); - _sfxChannels[i]->init(); - } - } - -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - if (_hasPercussion) { - _rhythmChannel = new TownsPC98_MusicChannelPCM(this, 0, 0, 0, 0, 0, 1); - _rhythmChannel->init(); - } -#endif - - setMusicTempo(84); - setSfxTempo(654); - - _ready = true; - - return true; -} - -void TownsPC98_AudioDriver::loadMusicData(uint8 *data, bool loadPaused) { - if (!_ready) { - warning("TownsPC98_AudioDriver: Driver must be initialized before loading data"); - return; - } - - if (!data) { - warning("TownsPC98_AudioDriver: Invalid music file data"); - return; - } - - reset(); - - Common::StackLock lock(_mutex); - uint8 *src_a = _trackPtr = _musicBuffer = data; - - for (uint8 i = 0; i < 3; i++) { - _channels[i]->loadData(data + READ_LE_UINT16(src_a)); - src_a += 2; - } - - for (int i = 0; i < _numSSG; i++) { - _ssgChannels[i]->loadData(data + READ_LE_UINT16(src_a)); - src_a += 2; - } - - for (uint8 i = 3; i < _numChan; i++) { - _channels[i]->loadData(data + READ_LE_UINT16(src_a)); - src_a += 2; - } - - if (_hasPercussion) { -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - _rhythmChannel->loadData(data + READ_LE_UINT16(src_a)); -#endif - src_a += 2; - } - - toggleRegProtection(false); - - _patches = src_a + 4; - _finishedChannelsFlag = _finishedSSGFlag = _finishedRhythmFlag = 0; - - _musicPlaying = (loadPaused ? false : true); -} - -void TownsPC98_AudioDriver::loadSoundEffectData(uint8 *data, uint8 trackNum) { - if (!_ready) { - warning("TownsPC98_AudioDriver: Driver must be initialized before loading data"); - return; - } - - if (!_sfxChannels) { - warning("TownsPC98_AudioDriver: Sound effects not supported by this configuration"); - return; - } - - if (!data) { - warning("TownsPC98_AudioDriver: Invalid sound effects file data"); - return; - } - - Common::StackLock lock(_mutex); - _sfxData = _sfxBuffer = data; - _sfxOffsets[0] = READ_LE_UINT16(&_sfxData[(trackNum << 2)]); - _sfxOffsets[1] = READ_LE_UINT16(&_sfxData[(trackNum << 2) + 2]); - _sfxPlaying = true; - _finishedSfxFlag = 0; -} - -void TownsPC98_AudioDriver::reset() { - Common::StackLock lock(_mutex); - - _musicPlaying = false; - _sfxPlaying = false; - _fading = false; - _looping = 0; - _musicTickCounter = 0; - _sfxData = 0; - - TownsPC98_FmSynth::reset(); - - for (int i = 0; i < _numChan; i++) - _channels[i]->reset(); - for (int i = 0; i < _numSSG; i++) - _ssgChannels[i]->reset(); - - if (_numSSG) { - for (int i = 0; i < 2; i++) - _sfxChannels[i]->reset(); - - memcpy(_ssgPatches, _drvTables + 156, 256); - } - -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - if (_rhythmChannel) - _rhythmChannel->reset(); -#endif -} - -void TownsPC98_AudioDriver::fadeStep() { - if (!_musicPlaying) - return; - - Common::StackLock lock(_mutex); - for (int j = 0; j < _numChan; j++) { - if (_updateChannelsFlag & _channels[j]->_idFlag) - _channels[j]->fadeStep(); - } - - for (int j = 0; j < _numSSG; j++) { - if (_updateSSGFlag & _ssgChannels[j]->_idFlag) - _ssgChannels[j]->fadeStep(); - } - - if (!_fading) { - _fading = 19; -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - if (_hasPercussion) { - if (_updateRhythmFlag & _rhythmChannel->_idFlag) - _rhythmChannel->reset(); - } -#endif - } else { - if (!--_fading) - reset(); - } -} - -void TownsPC98_AudioDriver::timerCallbackB() { - _sfxOffs = 0; - - if (_musicPlaying) { - _musicTickCounter++; - - for (int i = 0; i < _numChan; i++) { - if (_updateChannelsFlag & _channels[i]->_idFlag) { - _channels[i]->processEvents(); - _channels[i]->processFrequency(); - } - } - - for (int i = 0; i < _numSSG; i++) { - if (_updateSSGFlag & _ssgChannels[i]->_idFlag) { - _ssgChannels[i]->processEvents(); - _ssgChannels[i]->processFrequency(); - } - } - -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - if (_hasPercussion) - if (_updateRhythmFlag & _rhythmChannel->_idFlag) - _rhythmChannel->processEvents(); -#endif - } - - toggleRegProtection(false); - - if (_finishedChannelsFlag == _updateChannelsFlag && _finishedSSGFlag == _updateSSGFlag && _finishedRhythmFlag == _updateRhythmFlag) - _musicPlaying = false; -} - -void TownsPC98_AudioDriver::timerCallbackA() { - if (_sfxChannels && _sfxPlaying) { - if (_sfxData) - startSoundEffect(); - - _sfxOffs = 3; - _trackPtr = _sfxBuffer; - - for (int i = 0; i < 2; i++) { - if (_updateSfxFlag & _sfxChannels[i]->_idFlag) { - _sfxChannels[i]->processEvents(); - _sfxChannels[i]->processFrequency(); - } - } - - _trackPtr = _musicBuffer; - } - - if (_updateSfxFlag && _finishedSfxFlag == _updateSfxFlag) { - _sfxPlaying = false; - _updateSfxFlag = 0; - setVolumeChannelMasks(-1, 0); - } -} - -void TownsPC98_AudioDriver::setMusicTempo(uint8 tempo) { - writeReg(0, 0x26, tempo); - writeReg(0, 0x27, 0x33); -} - -void TownsPC98_AudioDriver::setSfxTempo(uint16 tempo) { - writeReg(0, 0x24, tempo & 0xff); - writeReg(0, 0x25, tempo >> 8); - writeReg(0, 0x27, 0x33); -} - -void TownsPC98_AudioDriver::startSoundEffect() { - int volFlags = 0; - - for (int i = 0; i < 2; i++) { - if (_sfxOffsets[i]) { - _ssgChannels[i + 1]->protect(); - _sfxChannels[i]->reset(); - _sfxChannels[i]->loadData(_sfxData + _sfxOffsets[i]); - _updateSfxFlag |= _sfxChannels[i]->_idFlag; - volFlags |= (_sfxChannels[i]->_idFlag << _numChan); - } else { - _ssgChannels[i + 1]->restore(); - _updateSfxFlag &= ~_sfxChannels[i]->_idFlag; - } - } - - setVolumeChannelMasks(~volFlags, volFlags); - _sfxData = 0; -} - -const uint8 TownsPC98_AudioDriver::_drvTables[] = { - // channel presets - 0x00, 0x80, 0x00, 0x00, 0x00, 0x01, - 0x01, 0x80, 0x01, 0x01, 0x00, 0x02, - 0x02, 0x80, 0x02, 0x02, 0x00, 0x04, - 0x00, 0x80, 0x03, 0x04, 0x01, 0x08, - 0x01, 0x80, 0x04, 0x05, 0x01, 0x10, - 0x02, 0x80, 0x05, 0x06, 0x01, 0x20, - - // control event size - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x05, - 0x02, 0x06, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, - - // fmt level presets - 0x54, 0x50, 0x4C, 0x48, 0x44, 0x40, 0x3C, 0x38, - 0x34, 0x30, 0x2C, 0x28, 0x24, 0x20, 0x1C, 0x18, - 0x14, 0x10, 0x0C, 0x08, 0x04, 0x90, 0x90, 0x90, - - // carriers - 0x08, 0x08, 0x08, 0x08, 0x0C, 0x0E, 0x0E, 0x0F, - - // pc98 level presets - 0x40, 0x3B, 0x38, 0x34, 0x30, 0x2A, 0x28, 0x25, - 0x22, 0x20, 0x1D, 0x1A, 0x18, 0x15, 0x12, 0x10, - 0x0D, 0x0A, 0x08, 0x05, 0x02, 0x90, 0x90, 0x90, - - // frequencies - 0x6A, 0x02, 0x8F, 0x02, 0xB6, 0x02, 0xDF, 0x02, - 0x0B, 0x03, 0x39, 0x03, 0x6A, 0x03, 0x9E, 0x03, - 0xD5, 0x03, 0x10, 0x04, 0x4E, 0x04, 0x8F, 0x04, - - // ssg frequencies - 0xE8, 0x0E, 0x12, 0x0E, 0x48, 0x0D, 0x89, 0x0C, - 0xD5, 0x0B, 0x2B, 0x0B, 0x8A, 0x0A, 0xF3, 0x09, - 0x64, 0x09, 0xDD, 0x08, 0x5E, 0x08, 0xE6, 0x07, - - // ssg patch data - 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00, - 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x37, 0x81, 0xC8, 0x00, - 0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x37, 0x81, 0xC8, 0x00, - 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00, - 0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00, - 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00, - 0x04, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00, - 0x0A, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0x01, 0x00, - 0xFF, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00, - 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0x00, - 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, - 0x64, 0x01, 0xFF, 0x64, 0xFF, 0x81, 0xFF, 0x00, - 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, - - 0x02, 0x01, 0xFF, 0x28, 0xFF, 0x81, 0xF0, 0x00, - 0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xC8, 0x00, - 0x01, 0x81, 0x00, 0x00, 0x28, 0x81, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0x78, 0x5F, 0x81, 0xA0, 0x00, - 0x05, 0x81, 0x00, 0x00, 0x28, 0x81, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00, - 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00, - 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00, - 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00 -}; - -#undef EUPHONY_FADEOUT_TICKS - diff --git a/sound/softsynth/fmtowns_pc98/towns_pc98_driver.h b/sound/softsynth/fmtowns_pc98/towns_pc98_driver.h deleted file mode 100644 index 00fcf7c5d5..0000000000 --- a/sound/softsynth/fmtowns_pc98/towns_pc98_driver.h +++ /dev/null @@ -1,135 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef TOWNS_PC98_AUDIODRIVER_H -#define TOWNS_PC98_AUDIODRIVER_H - -#include "sound/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h" - -class TownsPC98_MusicChannel; -class TownsPC98_MusicChannelSSG; -class TownsPC98_SfxChannel; -#ifndef DISABLE_PC98_RHYTHM_CHANNEL -class TownsPC98_MusicChannelPCM; -#endif - -class TownsPC98_AudioDriver : public TownsPC98_FmSynth { -friend class TownsPC98_MusicChannel; -friend class TownsPC98_MusicChannelSSG; -friend class TownsPC98_SfxChannel; -#ifndef DISABLE_PC98_RHYTHM_CHANNEL -friend class TownsPC98_MusicChannelPCM; -#endif -public: - TownsPC98_AudioDriver(Audio::Mixer *mixer, EmuType type); - ~TownsPC98_AudioDriver(); - - void loadMusicData(uint8 *data, bool loadPaused = false); - void loadSoundEffectData(uint8 *data, uint8 trackNum); - bool init(); - void reset(); - - void fadeStep(); - - void pause() { - _musicPlaying = false; - } - void cont() { - _musicPlaying = true; - } - - void timerCallbackB(); - void timerCallbackA(); - - bool looping() { - return _looping == _updateChannelsFlag ? true : false; - } - bool musicPlaying() { - return _musicPlaying; - } - - void setMusicVolume(int volume) { - _musicVolume = volume; - setVolumeIntern(_musicVolume, _sfxVolume); - } - void setSoundEffectVolume(int volume) { - _sfxVolume = volume; - setVolumeIntern(_musicVolume, _sfxVolume); - } - -protected: - void startSoundEffect(); - - void setMusicTempo(uint8 tempo); - void setSfxTempo(uint16 tempo); - - TownsPC98_MusicChannel **_channels; - TownsPC98_MusicChannelSSG **_ssgChannels; - TownsPC98_SfxChannel **_sfxChannels; -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - TownsPC98_MusicChannelPCM *_rhythmChannel; -#endif - - const uint8 *_opnCarrier; - const uint8 *_opnFreqTable; - const uint8 *_opnFreqTableSSG; - const uint8 *_opnFxCmdLen; - const uint8 *_opnLvlPresets; - - uint8 *_musicBuffer; - uint8 *_sfxBuffer; - uint8 *_trackPtr; - uint8 *_patches; - uint8 *_ssgPatches; - - uint8 _updateChannelsFlag; - uint8 _updateSSGFlag; - uint8 _updateRhythmFlag; - uint8 _updateSfxFlag; - uint8 _finishedChannelsFlag; - uint8 _finishedSSGFlag; - uint8 _finishedRhythmFlag; - uint8 _finishedSfxFlag; - - bool _musicPlaying; - bool _sfxPlaying; - uint8 _fading; - uint8 _looping; - uint32 _musicTickCounter; - - int _sfxOffs; - uint8 *_sfxData; - uint16 _sfxOffsets[2]; - - uint16 _musicVolume; - uint16 _sfxVolume; - - static const uint8 _drvTables[]; - - bool _ready; -}; - -#endif - diff --git a/sound/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp b/sound/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp deleted file mode 100644 index 62f7d39771..0000000000 --- a/sound/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp +++ /dev/null @@ -1,1548 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h" -#include "common/endian.h" - -class TownsPC98_FmSynthOperator { -public: - TownsPC98_FmSynthOperator(const uint32 timerbase, const uint32 rtt, const uint8 *rateTable, - const uint8 *shiftTable, const uint8 *attackDecayTable, const uint32 *frqTable, - const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable); - ~TownsPC98_FmSynthOperator() {} - - void keyOn(); - void keyOff(); - void frequency(int freq); - void updatePhaseIncrement(); - void recalculateRates(); - void generateOutput(int32 phasebuf, int32 *feedbuf, int32 &out); - - void feedbackLevel(int32 level) { - _feedbackLevel = level ? level + 6 : 0; - } - void detune(int value) { - _detn = &_detnTbl[value << 5]; - } - void multiple(uint32 value) { - _multiple = value ? (value << 1) : 1; - } - void attackRate(uint32 value) { - _specifiedAttackRate = value; - } - bool scaleRate(uint8 value); - void decayRate(uint32 value) { - _specifiedDecayRate = value; - recalculateRates(); - } - void sustainRate(uint32 value) { - _specifiedSustainRate = value; - recalculateRates(); - } - void sustainLevel(uint32 value) { - _sustainLevel = (value == 0x0f) ? 0x3e0 : value << 5; - } - void releaseRate(uint32 value) { - _specifiedReleaseRate = value; - recalculateRates(); - } - void totalLevel(uint32 value) { - _totalLevel = value << 3; - } - void ampModulation(bool enable) { - _ampMod = enable; - } - void reset(); - -protected: - EnvelopeState _state; - bool _playing; - uint32 _feedbackLevel; - uint32 _multiple; - uint32 _totalLevel; - uint8 _keyScale1; - uint8 _keyScale2; - uint32 _specifiedAttackRate; - uint32 _specifiedDecayRate; - uint32 _specifiedSustainRate; - uint32 _specifiedReleaseRate; - uint32 _tickCount; - uint32 _sustainLevel; - - bool _ampMod; - uint32 _frequency; - uint8 _kcode; - uint32 _phase; - uint32 _phaseIncrement; - const int32 *_detn; - - const uint8 *_rateTbl; - const uint8 *_rshiftTbl; - const uint8 *_adTbl; - const uint32 *_fTbl; - const uint32 *_sinTbl; - const int32 *_tLvlTbl; - const int32 *_detnTbl; - - const uint32 _tickLength; - uint32 _timer; - const uint32 _rtt; - int32 _currentLevel; - - struct EvpState { - uint8 rate; - uint8 shift; - } fs_a, fs_d, fs_s, fs_r; -}; - -TownsPC98_FmSynthOperator::TownsPC98_FmSynthOperator(const uint32 timerbase, const uint32 rtt, - const uint8 *rateTable, const uint8 *shiftTable, const uint8 *attackDecayTable, - const uint32 *frqTable, const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable) : - _rtt(rtt), _rateTbl(rateTable), _rshiftTbl(shiftTable), _adTbl(attackDecayTable), _fTbl(frqTable), - _sinTbl(sineTable), _tLvlTbl(tlevelOut), _detnTbl(detuneTable), _tickLength(timerbase * 2), - _specifiedAttackRate(0), _specifiedDecayRate(0), _specifiedReleaseRate(0), _specifiedSustainRate(0), - _phase(0), _state(kEnvReady), _playing(false), _timer(0), _keyScale1(0), - _keyScale2(0), _currentLevel(1023), _ampMod(false), _tickCount(0) { - - fs_a.rate = fs_a.shift = fs_d.rate = fs_d.shift = fs_s.rate = fs_s.shift = fs_r.rate = fs_r.shift = 0; - - reset(); -} - -void TownsPC98_FmSynthOperator::keyOn() { - if (_playing) - return; - - _playing = true; - _state = kEnvAttacking; - _phase = 0; -} - -void TownsPC98_FmSynthOperator::keyOff() { - if (!_playing) - return; - - _playing = false; - if (_state != kEnvReady) - _state = kEnvReleasing; -} - -void TownsPC98_FmSynthOperator::frequency(int freq) { - uint8 block = (freq >> 11); - uint16 pos = (freq & 0x7ff); - uint8 c = pos >> 7; - - _kcode = (block << 2) | ((c < 7) ? 0 : ((c > 8) ? 3 : c - 6)); - _frequency = _fTbl[pos << 1] >> (7 - block); -} - -void TownsPC98_FmSynthOperator::updatePhaseIncrement() { - _phaseIncrement = ((_frequency + _detn[_kcode]) * _multiple) >> 1; - uint8 keyscale = _kcode >> _keyScale1; - if (_keyScale2 != keyscale) { - _keyScale2 = keyscale; - recalculateRates(); - } -} - -void TownsPC98_FmSynthOperator::recalculateRates() { - int k = _keyScale2; - int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0; - fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136; - fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0; - - r = _specifiedDecayRate ? (_specifiedDecayRate << 1) + 0x20 : 0; - fs_d.rate = _rateTbl[r + k]; - fs_d.shift = _rshiftTbl[r + k]; - - r = _specifiedSustainRate ? (_specifiedSustainRate << 1) + 0x20 : 0; - fs_s.rate = _rateTbl[r + k]; - fs_s.shift = _rshiftTbl[r + k]; - - r = (_specifiedReleaseRate << 2) + 0x22; - fs_r.rate = _rateTbl[r + k]; - fs_r.shift = _rshiftTbl[r + k]; -} - -void TownsPC98_FmSynthOperator::generateOutput(int32 phasebuf, int32 *feed, int32 &out) { - if (_state == kEnvReady) - return; - - _timer += _tickLength; - while (_timer > _rtt) { - _timer -= _rtt; - ++_tickCount; - - int32 levelIncrement = 0; - uint32 targetTime = 0; - int32 targetLevel = 0; - EnvelopeState nextState = kEnvReady; - - switch (_state) { - case kEnvReady: - return; - case kEnvAttacking: - targetLevel = 0; - nextState = kEnvDecaying; - if ((_specifiedAttackRate << 1) + _keyScale2 < 64) { - targetTime = (1 << fs_a.shift) - 1; - levelIncrement = (~_currentLevel * _adTbl[fs_a.rate + ((_tickCount >> fs_a.shift) & 7)]) >> 4; - break; - } else { - _currentLevel = targetLevel; - _state = nextState; - } - // Fall through - case kEnvDecaying: - targetTime = (1 << fs_d.shift) - 1; - nextState = kEnvSustaining; - targetLevel = _sustainLevel; - levelIncrement = _adTbl[fs_d.rate + ((_tickCount >> fs_d.shift) & 7)]; - break; - case kEnvSustaining: - targetTime = (1 << fs_s.shift) - 1; - nextState = kEnvSustaining; - targetLevel = 1023; - levelIncrement = _adTbl[fs_s.rate + ((_tickCount >> fs_s.shift) & 7)]; - break; - case kEnvReleasing: - targetTime = (1 << fs_r.shift) - 1; - nextState = kEnvReady; - targetLevel = 1023; - levelIncrement = _adTbl[fs_r.rate + ((_tickCount >> fs_r.shift) & 7)]; - break; - } - - if (!(_tickCount & targetTime)) { - _currentLevel += levelIncrement; - if ((_state == kEnvAttacking && _currentLevel <= targetLevel) || (_state != kEnvAttacking && _currentLevel >= targetLevel)) { - if (_state != kEnvDecaying) - _currentLevel = targetLevel; - _state = nextState; - } - } - } - - uint32 lvlout = _totalLevel + (uint32) _currentLevel; - - - int32 outp = 0; - int32 *i = &outp, *o = &outp; - int phaseShift = 0; - - if (feed) { - o = &feed[0]; - i = &feed[1]; - phaseShift = _feedbackLevel ? ((*o + *i) << _feedbackLevel) : 0; - *o = *i; - } else { - phaseShift = phasebuf << 15; - } - - if (lvlout < 832) { - uint32 index = (lvlout << 3) + _sinTbl[(((int32)((_phase & 0xffff0000) - + phaseShift)) >> 16) & 0x3ff]; - *i = ((index < 6656) ? _tLvlTbl[index] : 0); - } else { - *i = 0; - } - - _phase += _phaseIncrement; - out += *o; -} - -void TownsPC98_FmSynthOperator::reset() { - keyOff(); - _timer = 0; - _keyScale2 = 0; - _currentLevel = 1023; - - frequency(0); - detune(0); - scaleRate(0); - multiple(0); - updatePhaseIncrement(); - attackRate(0); - decayRate(0); - releaseRate(0); - sustainRate(0); - feedbackLevel(0); - totalLevel(127); - ampModulation(false); -} - -bool TownsPC98_FmSynthOperator::scaleRate(uint8 value) { - value = 3 - value; - if (_keyScale1 != value) { - _keyScale1 = value; - return true; - } - - int k = _keyScale2; - int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0; - fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136; - fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0; - return false; -} - -class TownsPC98_FmSynthSquareSineSource { -public: - TownsPC98_FmSynthSquareSineSource(const uint32 timerbase, const uint32 rtt); - ~TownsPC98_FmSynthSquareSineSource(); - - void init(const int *rsTable, const int *rseTable); - void reset(); - void writeReg(uint8 address, uint8 value, bool force = false); - - void nextTick(int32 *buffer, uint32 bufferSize); - - void setVolumeIntern(int volA, int volB) { - _volumeA = volA; - _volumeB = volB; - } - void setVolumeChannelMasks(int channelMaskA, int channelMaskB) { - _volMaskA = channelMaskA; - _volMaskB = channelMaskB; - } - - uint8 chanEnable() const { - return _chanEnable; - } -private: - void updateRegs(); - - uint8 _updateRequestBuf[64]; - int _updateRequest; - int _rand; - - int8 _evpTimer; - uint32 _pReslt; - uint8 _attack; - - bool _evpUpdate, _cont; - - int _evpUpdateCnt; - uint8 _outN; - int _nTick; - - int32 *_tlTable; - int32 *_tleTable; - - const uint32 _tickLength; - uint32 _timer; - const uint32 _rtt; - - struct Channel { - int tick; - uint8 smp; - uint8 out; - - uint8 frqL; - uint8 frqH; - uint8 vol; - } _channels[3]; - - uint8 _noiseGenerator; - uint8 _chanEnable; - - uint8 **_reg; - - uint16 _volumeA; - uint16 _volumeB; - int _volMaskA; - int _volMaskB; - - bool _ready; -}; - -#ifndef DISABLE_PC98_RHYTHM_CHANNEL -class TownsPC98_FmSynthPercussionSource { -public: - TownsPC98_FmSynthPercussionSource(const uint32 timerbase, const uint32 rtt); - ~TownsPC98_FmSynthPercussionSource() { - delete[] _reg; - } - - void init(const uint8 *instrData = 0); - void reset(); - void writeReg(uint8 address, uint8 value); - - void nextTick(int32 *buffer, uint32 bufferSize); - - void setVolumeIntern(int volA, int volB) { - _volumeA = volA; - _volumeB = volB; - } - void setVolumeChannelMasks(int channelMaskA, int channelMaskB) { - _volMaskA = channelMaskA; - _volMaskB = channelMaskB; - } - -private: - struct RhtChannel { - const uint8 *data; - - const uint8 *start; - const uint8 *end; - const uint8 *pos; - uint32 size; - bool active; - uint8 level; - - int8 decState; - uint8 decStep; - - int16 samples[2]; - int out; - - uint8 startPosH; - uint8 startPosL; - uint8 endPosH; - uint8 endPosL; - }; - - void recalcOuput(RhtChannel *ins); - void advanceInput(RhtChannel *ins); - - RhtChannel _rhChan[6]; - - uint8 _totalLevel; - - const uint32 _tickLength; - uint32 _timer; - const uint32 _rtt; - - uint8 **_reg; - - uint16 _volumeA; - uint16 _volumeB; - int _volMaskA; - int _volMaskB; - - bool _ready; -}; -#endif // DISABLE_PC98_RHYTHM_CHANNEL - -TownsPC98_FmSynthSquareSineSource::TownsPC98_FmSynthSquareSineSource(const uint32 timerbase, const uint32 rtt) : _tlTable(0), - _rtt(rtt), _tleTable(0), _updateRequest(-1), _tickLength(timerbase * 27), _ready(0), _reg(0), _rand(1), _outN(1), - _nTick(0), _evpUpdateCnt(0), _evpTimer(0x1f), _pReslt(0x1f), _attack(0), _cont(false), _evpUpdate(true), - _timer(0), _noiseGenerator(0), _chanEnable(0), - _volMaskA(0), _volMaskB(0), _volumeA(Audio::Mixer::kMaxMixerVolume), _volumeB(Audio::Mixer::kMaxMixerVolume) { - - memset(_channels, 0, sizeof(_channels)); - memset(_updateRequestBuf, 0, sizeof(_updateRequestBuf)); - _reg = new uint8 *[11]; - - _reg[0] = &_channels[0].frqL; - _reg[1] = &_channels[0].frqH; - _reg[2] = &_channels[1].frqL; - _reg[3] = &_channels[1].frqH; - _reg[4] = &_channels[2].frqL; - _reg[5] = &_channels[2].frqH; - _reg[6] = &_noiseGenerator; - _reg[7] = &_chanEnable; - _reg[8] = &_channels[0].vol; - _reg[9] = &_channels[1].vol; - _reg[10] = &_channels[2].vol; - - reset(); -} - -TownsPC98_FmSynthSquareSineSource::~TownsPC98_FmSynthSquareSineSource() { - delete[] _tlTable; - delete[] _tleTable; - delete[] _reg; -} - -void TownsPC98_FmSynthSquareSineSource::init(const int *rsTable, const int *rseTable) { - if (_ready) { - reset(); - return; - } - - delete[] _tlTable; - delete[] _tleTable; - _tlTable = new int32[16]; - _tleTable = new int32[32]; - float a, b, d; - d = 801.0f; - - for (int i = 0; i < 16; i++) { - b = 1.0f / rsTable[i]; - a = 1.0f / d + b + 1.0f / 1000.0f; - float v = (b / a) * 32767.0f; - _tlTable[i] = (int32) v; - - b = 1.0f / rseTable[i]; - a = 1.0f / d + b + 1.0f / 1000.0f; - v = (b / a) * 32767.0f; - _tleTable[i] = (int32) v; - } - - for (int i = 16; i < 32; i++) { - b = 1.0f / rseTable[i]; - a = 1.0f / d + b + 1.0f / 1000.0f; - float v = (b / a) * 32767.0f; - _tleTable[i] = (int32) v; - } - - _ready = true; -} - -void TownsPC98_FmSynthSquareSineSource::reset() { - _rand = 1; - _outN = 1; - _updateRequest = -1; - _nTick = _evpUpdateCnt = 0; - _evpTimer = 0x1f; - _pReslt = 0x1f; - _attack = 0; - _cont = false; - _evpUpdate = true; - _timer = 0; - - for (int i = 0; i < 3; i++) { - _channels[i].tick = 0; - _channels[i].smp = _channels[i].out = 0; - } - - for (int i = 0; i < 14; i++) - writeReg(i, 0, true); - - writeReg(7, 0xbf, true); -} - -void TownsPC98_FmSynthSquareSineSource::writeReg(uint8 address, uint8 value, bool force) { - if (!_ready) - return; - - if (address > 10 || *_reg[address] == value) { - if ((address == 11 || address == 12 || address == 13) && value) - warning("TownsPC98_FmSynthSquareSineSource: unsupported reg address: %d", address); - return; - } - - if (!force) { - if (_updateRequest >= 63) { - warning("TownsPC98_FmSynthSquareSineSource: event buffer overflow"); - _updateRequest = -1; - } - _updateRequestBuf[++_updateRequest] = value; - _updateRequestBuf[++_updateRequest] = address; - return; - } - - *_reg[address] = value; -} - -void TownsPC98_FmSynthSquareSineSource::nextTick(int32 *buffer, uint32 bufferSize) { - if (!_ready) - return; - - for (uint32 i = 0; i < bufferSize; i++) { - _timer += _tickLength; - while (_timer > _rtt) { - _timer -= _rtt; - - if (++_nTick >= (_noiseGenerator & 0x1f)) { - if ((_rand + 1) & 2) - _outN ^= 1; - - _rand = (((_rand & 1) ^ ((_rand >> 3) & 1)) << 16) | (_rand >> 1); - _nTick = 0; - } - - for (int ii = 0; ii < 3; ii++) { - if (++_channels[ii].tick >= (((_channels[ii].frqH & 0x0f) << 8) | _channels[ii].frqL)) { - _channels[ii].tick = 0; - _channels[ii].smp ^= 1; - } - _channels[ii].out = (_channels[ii].smp | ((_chanEnable >> ii) & 1)) & (_outN | ((_chanEnable >> (ii + 3)) & 1)); - } - - if (_evpUpdate) { - if (++_evpUpdateCnt >= 0) { - _evpUpdateCnt = 0; - - if (--_evpTimer < 0) { - if (_cont) { - _evpTimer &= 0x1f; - } else { - _evpUpdate = false; - _evpTimer = 0; - } - } - } - } - _pReslt = _evpTimer ^ _attack; - updateRegs(); - } - - int32 finOut = 0; - for (int ii = 0; ii < 3; ii++) { - int32 finOutTemp = ((_channels[ii].vol >> 4) & 1) ? _tleTable[_channels[ii].out ? _pReslt : 0] : _tlTable[_channels[ii].out ? (_channels[ii].vol & 0x0f) : 0]; - - if ((1 << ii) & _volMaskA) - finOutTemp = (finOutTemp * _volumeA) / Audio::Mixer::kMaxMixerVolume; - - if ((1 << ii) & _volMaskB) - finOutTemp = (finOutTemp * _volumeB) / Audio::Mixer::kMaxMixerVolume; - - finOut += finOutTemp; - } - - finOut /= 3; - - buffer[i << 1] += finOut; - buffer[(i << 1) + 1] += finOut; - } -} - -void TownsPC98_FmSynthSquareSineSource::updateRegs() { - for (int i = 0; i < _updateRequest;) { - uint8 b = _updateRequestBuf[i++]; - uint8 a = _updateRequestBuf[i++]; - writeReg(a, b, true); - } - _updateRequest = -1; -} - -#ifndef DISABLE_PC98_RHYTHM_CHANNEL -TownsPC98_FmSynthPercussionSource::TownsPC98_FmSynthPercussionSource(const uint32 timerbase, const uint32 rtt) : - _rtt(rtt), _tickLength(timerbase * 2), _timer(0), _ready(false), _volMaskA(0), _volMaskB(0), _volumeA(Audio::Mixer::kMaxMixerVolume), _volumeB(Audio::Mixer::kMaxMixerVolume) { - - memset(_rhChan, 0, sizeof(RhtChannel) * 6); - _reg = new uint8 *[40]; - - _reg[0] = _reg[1] = _reg[2] = _reg[3] = _reg[4] = _reg[5] = _reg[6] = _reg[7] = _reg[8] = _reg[9] = _reg[10] = _reg[11] = _reg[12] = _reg[13] = _reg[14] = _reg[15] = 0; - _reg[16] = &_rhChan[0].startPosL; - _reg[17] = &_rhChan[1].startPosL; - _reg[18] = &_rhChan[2].startPosL; - _reg[19] = &_rhChan[3].startPosL; - _reg[20] = &_rhChan[4].startPosL; - _reg[21] = &_rhChan[5].startPosL; - _reg[22] = &_rhChan[0].startPosH; - _reg[23] = &_rhChan[1].startPosH; - _reg[24] = &_rhChan[2].startPosH; - _reg[25] = &_rhChan[3].startPosH; - _reg[26] = &_rhChan[4].startPosH; - _reg[27] = &_rhChan[5].startPosH; - _reg[28] = &_rhChan[0].endPosL; - _reg[29] = &_rhChan[1].endPosL; - _reg[30] = &_rhChan[2].endPosL; - _reg[31] = &_rhChan[3].endPosL; - _reg[32] = &_rhChan[4].endPosL; - _reg[33] = &_rhChan[5].endPosL; - _reg[34] = &_rhChan[0].endPosH; - _reg[35] = &_rhChan[1].endPosH; - _reg[36] = &_rhChan[2].endPosH; - _reg[37] = &_rhChan[3].endPosH; - _reg[38] = &_rhChan[4].endPosH; - _reg[39] = &_rhChan[5].endPosH; -} - -void TownsPC98_FmSynthPercussionSource::init(const uint8 *instrData) { - if (_ready) { - reset(); - return; - } - - const uint8 *start = instrData; - const uint8 *pos = start; - - if (instrData) { - for (int i = 0; i < 6; i++) { - _rhChan[i].data = start + READ_BE_UINT16(pos); - pos += 2; - _rhChan[i].size = READ_BE_UINT16(pos); - pos += 2; - } - reset(); - _ready = true; - } else { - memset(_rhChan, 0, sizeof(RhtChannel) * 6); - _ready = false; - } -} - -void TownsPC98_FmSynthPercussionSource::reset() { - _timer = 0; - _totalLevel = 63; - - for (int i = 0; i < 6; i++) { - RhtChannel *s = &_rhChan[i]; - s->pos = s->start = s->data; - s->end = s->data + s->size; - s->active = false; - s->level = 0; - s->out = 0; - s->decStep = 1; - s->decState = 0; - s->samples[0] = s->samples[1] = 0; - s->startPosH = s->startPosL = s->endPosH = s->endPosL = 0; - } -} - -void TownsPC98_FmSynthPercussionSource::writeReg(uint8 address, uint8 value) { - if (!_ready) - return; - - uint8 h = address >> 4; - uint8 l = address & 15; - - if (address > 15) - *_reg[address] = value; - - if (address == 0) { - if (value & 0x80) { - //key off - for (int i = 0; i < 6; i++) { - if ((value >> i) & 1) - _rhChan[i].active = false; - } - } else { - //key on - for (int i = 0; i < 6; i++) { - if ((value >> i) & 1) { - RhtChannel *s = &_rhChan[i]; - s->pos = s->start; - s->active = true; - s->out = 0; - s->samples[0] = s->samples[1] = 0; - s->decStep = 1; - s->decState = 0; - } - } - } - } else if (address == 1) { - // total level - _totalLevel = (value & 63) ^ 63; - for (int i = 0; i < 6; i++) - recalcOuput(&_rhChan[i]); - } else if (!h && l & 8) { - // instrument level - l &= 7; - _rhChan[l].level = (value & 0x1f) ^ 0x1f; - recalcOuput(&_rhChan[l]); - } else if (h & 3) { - l &= 7; - if (h == 1) { - // set start offset - _rhChan[l].start = _rhChan[l].data + ((_rhChan[l].startPosH << 8 | _rhChan[l].startPosL) << 8); - } else if (h == 2) { - // set end offset - _rhChan[l].end = _rhChan[l].data + ((_rhChan[l].endPosH << 8 | _rhChan[l].endPosL) << 8) + 255; - } - } -} - -void TownsPC98_FmSynthPercussionSource::nextTick(int32 *buffer, uint32 bufferSize) { - if (!_ready) - return; - - for (uint32 i = 0; i < bufferSize; i++) { - _timer += _tickLength; - while (_timer > _rtt) { - _timer -= _rtt; - - for (int ii = 0; ii < 6; ii++) { - RhtChannel *s = &_rhChan[ii]; - if (s->active) { - recalcOuput(s); - if (s->decStep) { - advanceInput(s); - if (s->pos == s->end) - s->active = false; - } - s->decStep ^= 1; - } - } - } - - int32 finOut = 0; - - for (int ii = 0; ii < 6; ii++) { - if (_rhChan[ii].active) - finOut += _rhChan[ii].out; - } - - finOut <<= 1; - - if (1 & _volMaskA) - finOut = (finOut * _volumeA) / Audio::Mixer::kMaxMixerVolume; - - if (1 & _volMaskB) - finOut = (finOut * _volumeB) / Audio::Mixer::kMaxMixerVolume; - - buffer[i << 1] += finOut; - buffer[(i << 1) + 1] += finOut; - } -} - -void TownsPC98_FmSynthPercussionSource::recalcOuput(RhtChannel *ins) { - uint32 s = _totalLevel + ins->level; - uint32 x = s > 62 ? 0 : (1 + (s >> 3)); - int32 y = s > 62 ? 0 : (15 - (s & 7)); - ins->out = ((ins->samples[ins->decStep] * y) >> x) & ~3; -} - -void TownsPC98_FmSynthPercussionSource::advanceInput(RhtChannel *ins) { - static const int8 adjustIndex[] = { -1, -1, -1, -1, 2, 5, 7, 9 }; - - static const int16 stepTable[] = { - 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, - 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, - 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552 - }; - - uint8 cur = (int8)*ins->pos++; - - for (int i = 0; i < 2; i++) { - int b = (2 * (cur & 7) + 1) * stepTable[ins->decState] / 8; - ins->samples[i] = CLIP(ins->samples[i ^ 1] + (cur & 8 ? b : -b), -2048, 2047); - ins->decState = CLIP(ins->decState + adjustIndex[cur & 7], 0, 48); - cur >>= 4; - } -} -#endif // DISABLE_PC98_RHYTHM_CHANNEL - -TownsPC98_FmSynth::TownsPC98_FmSynth(Audio::Mixer *mixer, EmuType type) : - _mixer(mixer), - _chanInternal(0), _ssg(0), -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - _prc(0), -#endif - _numChan(type == kType26 ? 3 : 6), _numSSG(type == kTypeTowns ? 0 : 3), - _hasPercussion(type == kType86 ? true : false), - _oprRates(0), _oprRateshift(0), _oprAttackDecay(0), _oprFrq(0), _oprSinTbl(0), _oprLevelOut(0), _oprDetune(0), - _rtt(type == kTypeTowns ? 0x514767 : 0x5B8D80), _baserate(55125.0f / (float)mixer->getOutputRate()), - _volMaskA(0), _volMaskB(0), _volumeA(255), _volumeB(255), - _regProtectionFlag(false), _ready(false) { - - memset(&_timers[0], 0, sizeof(ChipTimer)); - memset(&_timers[1], 0, sizeof(ChipTimer)); - - _timers[0].cb = _timers[1].cb = &TownsPC98_FmSynth::idleTimerCallback; - _timerbase = (uint32)(_baserate * 1000000.0f); -} - -TownsPC98_FmSynth::~TownsPC98_FmSynth() { - if (_ready) - deinit(); - - delete _ssg; -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - delete _prc; -#endif - delete[] _chanInternal; - - delete[] _oprRates; - delete[] _oprRateshift; - delete[] _oprFrq; - delete[] _oprAttackDecay; - delete[] _oprSinTbl; - delete[] _oprLevelOut; - delete[] _oprDetune; -} - -bool TownsPC98_FmSynth::init() { - if (_ready) { - reset(); - return true; - } - - generateTables(); - - _chanInternal = new ChanInternal[_numChan]; - for (int i = 0; i < _numChan; i++) { - memset(&_chanInternal[i], 0, sizeof(ChanInternal)); - for (int j = 0; j < 4; ++j) - _chanInternal[i].opr[j] = new TownsPC98_FmSynthOperator(_timerbase, _rtt, _oprRates, _oprRateshift, _oprAttackDecay, _oprFrq, _oprSinTbl, _oprLevelOut, _oprDetune); - } - - if (_numSSG) { - _ssg = new TownsPC98_FmSynthSquareSineSource(_timerbase, _rtt); - _ssg->init(&_ssgTables[0], &_ssgTables[16]); - } - -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - if (_hasPercussion) { - _prc = new TownsPC98_FmSynthPercussionSource(_timerbase, _rtt); - _prc->init(_percussionData); - } -#endif - - _timers[0].cb = &TownsPC98_FmSynth::timerCallbackA; - _timers[1].cb = &TownsPC98_FmSynth::timerCallbackB; - - _mixer->playStream(Audio::Mixer::kPlainSoundType, - &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); - - _ready = true; - - return true; -} - -void TownsPC98_FmSynth::reset() { - Common::StackLock lock(_mutex); - for (int i = 0; i < _numChan; i++) { - for (int ii = 0; ii < 4; ii++) - _chanInternal[i].opr[ii]->reset(); - memset(_chanInternal[i].feedbuf, 0, 3); - _chanInternal[i].algorithm = 0; - _chanInternal[i].frqTemp = 0; - _chanInternal[i].enableLeft = _chanInternal[i].enableRight = true; - _chanInternal[i].updateEnvelopeParameters = false; - } - - writeReg(0, 0x27, 0x33); - - if (_ssg) - _ssg->reset(); - -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - if (_prc) - _prc->reset(); -#endif -} - -void TownsPC98_FmSynth::writeReg(uint8 part, uint8 regAddress, uint8 value) { - if (_regProtectionFlag || !_ready) - return; - - static const uint8 oprOrdr[] = { 0, 2, 1, 3 }; - - Common::StackLock lock(_mutex); - - uint8 h = regAddress & 0xf0; - uint8 l = (regAddress & 0x0f); - - ChanInternal *c = 0; - TownsPC98_FmSynthOperator **co = 0; - TownsPC98_FmSynthOperator *o = 0; - - if (regAddress > 0x2F) { - c = &_chanInternal[(l & 3) + 3 * part]; - co = c->opr; - o = c->opr[oprOrdr[(l - (l & 3)) >> 2]]; - } else if (regAddress == 0x28) { - c = &_chanInternal[(value & 3) + ((value & 4) ? 3 : 0)]; - co = c->opr; - } - - switch (h) { - case 0x00: - // ssg - if (_ssg) - _ssg->writeReg(l, value); - break; - case 0x10: -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - // pcm rhythm channel - if (_prc) - _prc->writeReg(l, value); -#endif - break; - case 0x20: - if (l == 8) { - // Key on/off - for (int i = 0; i < 4; i++) { - if ((value >> (4 + i)) & 1) - co[oprOrdr[i]]->keyOn(); - else - co[oprOrdr[i]]->keyOff(); - } - } else if (l == 4) { - // Timer A - _timers[0].value = (_timers[0].value & 3) | (value << 2); - } else if (l == 5) { - // Timer A - _timers[0].value = (_timers[0].value & 0x3fc) | (value & 3); - } else if (l == 6) { - // Timer B - _timers[1].value = value & 0xff; - } else if (l == 7) { - if (value & 1) { - float spc = (float)(0x400 - _timers[0].value) / _baserate; - if (spc < 1) { - warning("TownsPC98_FmSynth: Invalid Timer A setting: %d", _timers[0].value); - spc = 1; - } - - _timers[0].smpPerCb = (int32) spc; - _timers[0].smpPerCbRem = (uint32)((spc - (float)_timers[0].smpPerCb) * 1000000.0f); - _timers[0].smpTillCb = _timers[0].smpPerCb; - _timers[0].smpTillCbRem = _timers[0].smpPerCbRem; - _timers[0].enabled = true; - } else { - _timers[0].enabled = false; - } - - if (value & 2) { - float spc = (float)(0x100 - _timers[1].value) * 16.0f / _baserate; - if (spc < 1) { - warning("TownsPC98_FmSynth: Invalid Timer B setting: %d", _timers[1].value); - spc = 1; - } - - _timers[1].smpPerCb = (int32) spc; - _timers[1].smpPerCbRem = (uint32)((spc - (float)_timers[1].smpPerCb) * 1000000.0f); - _timers[1].smpTillCb = _timers[1].smpPerCb; - _timers[1].smpTillCbRem = _timers[1].smpPerCbRem; - _timers[1].enabled = true; - } else { - _timers[1].enabled = false; - } - - if (value & 0x10) { - _timers[0].smpTillCb = _timers[0].smpPerCb; - _timers[0].smpTillCbRem = _timers[0].smpTillCbRem; - } - - if (value & 0x20) { - _timers[1].smpTillCb = _timers[1].smpPerCb; - _timers[1].smpTillCbRem = _timers[1].smpTillCbRem; - } - } else if (l == 2) { - // LFO - if (value & 8) - warning("TownsPC98_FmSynth: TRYING TO USE LFO (NOT SUPPORTED)"); - } else if (l == 10 || l == 11) { - // DAC - if (l == 11 && (value & 0x80)) - warning("TownsPC98_FmSynth: TRYING TO USE DAC (NOT SUPPORTED)"); - } - break; - - case 0x30: - // detune, multiple - o->detune((value >> 4) & 7); - o->multiple(value & 0x0f); - c->updateEnvelopeParameters = true; - break; - - case 0x40: - // total level - o->totalLevel(value & 0x7f); - break; - - case 0x50: - // rate scaling, attack rate - o->attackRate(value & 0x1f); - if (o->scaleRate(value >> 6)) - c->updateEnvelopeParameters = true; - break; - - case 0x60: - // first decay rate, amplitude modulation - o->decayRate(value & 0x1f); - o->ampModulation(value & 0x80 ? true : false); - break; - - case 0x70: - // secondary decay rate - o->sustainRate(value & 0x1f); - break; - - case 0x80: - // secondary amplitude, release rate; - o->sustainLevel(value >> 4); - o->releaseRate(value & 0x0f); - break; - - case 0x90: - warning("TownsPC98_FmSynth: TRYING TO USE SSG ENVELOPE SHAPES (NOT SUPPORTED)"); - break; - - case 0xa0: - // frequency - l &= ~3; - if (l == 0) { - c->frqTemp = (c->frqTemp & 0xff00) | value; - c->updateEnvelopeParameters = true; - for (int i = 0; i < 4; i++) - co[i]->frequency(c->frqTemp); - } else if (l == 4) { - c->frqTemp = (c->frqTemp & 0xff) | (value << 8); - } else if (l == 8) { - // Ch 3/6 special mode frq - warning("TownsPC98_FmSynth: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)"); - } else if (l == 12) { - // Ch 3/6 special mode frq - warning("TownsPC98_FmSynth: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)"); - } - break; - - case 0xb0: - l &= ~3; - if (l == 0) { - // feedback, _algorithm - co[0]->feedbackLevel((value >> 3) & 7); - c->algorithm = value & 7; - } else if (l == 4) { - // stereo, LFO sensitivity - c->enableLeft = value & 0x80 ? true : false; - c->enableRight = value & 0x40 ? true : false; - c->ampModSensitivity((value & 0x30) >> 4); - c->frqModSensitivity(value & 3); - } - break; - - default: - warning("TownsPC98_FmSynth: UNKNOWN ADDRESS %d", regAddress); - } -} - -int TownsPC98_FmSynth::readBuffer(int16 *buffer, const int numSamples) { - Common::StackLock lock(_mutex); - - memset(buffer, 0, sizeof(int16) * numSamples); - int32 *tmp = new int32[numSamples]; - int32 *tmpStart = tmp; - memset(tmp, 0, sizeof(int32) * numSamples); - int32 samplesLeft = numSamples >> 1; - - while (_ready && samplesLeft) { - int32 render = samplesLeft; - - for (int i = 0; i < 2; i++) { - if (_timers[i].enabled && _timers[i].cb) { - if (!_timers[i].smpTillCb) { - (this->*_timers[i].cb)(); - _timers[i].smpTillCb = _timers[i].smpPerCb; - - _timers[i].smpTillCbRem += _timers[i].smpPerCbRem; - if (_timers[i].smpTillCbRem >= _timerbase) { - _timers[i].smpTillCb++; - _timers[i].smpTillCbRem -= _timerbase; - } - } - render = MIN(render, _timers[i].smpTillCb); - } - } - - samplesLeft -= render; - - for (int i = 0; i < 2; i++) { - if (_timers[i].enabled && _timers[i].cb) { - _timers[i].smpTillCb -= render; - } - } - - nextTick(tmp, render); - - if (_ssg) - _ssg->nextTick(tmp, render); -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - if (_prc) - _prc->nextTick(tmp, render); -#endif - - nextTickEx(tmp, render); - - for (int i = 0; i < render; ++i) { - int32 l = CLIP(tmp[i << 1], -32767, 32767); - buffer[i << 1] = (int16) l; - int32 r = CLIP(tmp[(i << 1) + 1], -32767, 32767); - buffer[(i << 1) + 1] = (int16) r; - } - - buffer += (render << 1); - tmp += (render << 1); - } - - delete[] tmpStart; - return numSamples; -} - -void TownsPC98_FmSynth::deinit() { - _ready = false; - _mixer->stopHandle(_soundHandle); - _timers[0].cb = _timers[1].cb = &TownsPC98_FmSynth::idleTimerCallback; -} - -uint8 TownsPC98_FmSynth::readSSGStatus() { - return _ssg->chanEnable(); -} - -void TownsPC98_FmSynth::setVolumeIntern(int volA, int volB) { - Common::StackLock lock(_mutex); - _volumeA = CLIP(volA, 0, Audio::Mixer::kMaxMixerVolume); - _volumeB = CLIP(volB, 0, Audio::Mixer::kMaxMixerVolume); - if (_ssg) - _ssg->setVolumeIntern(_volumeA, _volumeB); -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - if (_prc) - _prc->setVolumeIntern(_volumeA, _volumeB); -#endif -} - -void TownsPC98_FmSynth::setVolumeChannelMasks(int channelMaskA, int channelMaskB) { - Common::StackLock lock(_mutex); - _volMaskA = channelMaskA; - _volMaskB = channelMaskB; - if (_ssg) - _ssg->setVolumeChannelMasks(_volMaskA >> _numChan, _volMaskB >> _numChan); -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - if (_prc) - _prc->setVolumeChannelMasks(_volMaskA >> (_numChan + _numSSG), _volMaskB >> (_numChan + _numSSG)); -#endif -} - -void TownsPC98_FmSynth::generateTables() { - delete[] _oprRates; - _oprRates = new uint8[128]; - - WRITE_BE_UINT32(_oprRates + 32, _numChan == 6 ? 0x90900000 : 0x00081018); - WRITE_BE_UINT32(_oprRates + 36, _numChan == 6 ? 0x00001010 : 0x00081018); - memset(_oprRates, 0x90, 32); - memset(_oprRates + 96, 0x80, 32); - uint8 *dst = (uint8 *)_oprRates + 40; - for (int i = 0; i < 40; i += 4) - WRITE_BE_UINT32(dst + i, 0x00081018); - for (int i = 0; i < 48; i += 4) - WRITE_BE_UINT32(dst + i, 0x00081018); - dst += 40; - for (uint8 i = 0; i < 16; i ++) { - uint8 v = (i < 12) ? i : 12; - *dst++ = ((4 + v) << 3); - } - - delete[] _oprRateshift; - _oprRateshift = new uint8[128]; - memset(_oprRateshift, 0, 128); - dst = (uint8 *)_oprRateshift + 32; - for (int i = 11; i; i--) { - memset(dst, i, 4); - dst += 4; - } - - delete[] _oprFrq; - _oprFrq = new uint32[0x1000]; - for (uint32 i = 0; i < 0x1000; i++) - _oprFrq[i] = (uint32)(_baserate * (float)(i << 11)); - - delete[] _oprAttackDecay; - _oprAttackDecay = new uint8[152]; - memset(_oprAttackDecay, 0, 152); - for (int i = 0; i < 36; i++) - WRITE_BE_UINT32(_oprAttackDecay + (i << 2), _adtStat[i]); - - delete[] _oprSinTbl; - _oprSinTbl = new uint32[1024]; - for (int i = 0; i < 1024; i++) { - double val = sin((double)(((i << 1) + 1) * PI / 1024.0)); - double d_dcb = log(1.0 / (double)ABS(val)) / log(2.0) * 256.0; - int32 i_dcb = (int32)(2.0 * d_dcb); - i_dcb = (i_dcb & 1) ? (i_dcb >> 1) + 1 : (i_dcb >> 1); - _oprSinTbl[i] = (i_dcb << 1) + (val >= 0.0 ? 0 : 1); - } - - delete[] _oprLevelOut; - _oprLevelOut = new int32[0x1a00]; - for (int i = 0; i < 256; i++) { - double val = floor(65536.0 / pow(2.0, 0.00390625 * (double)(1 + i))); - int32 val_int = ((int32) val) >> 4; - _oprLevelOut[i << 1] = (val_int & 1) ? ((val_int >> 1) + 1) << 2 : (val_int >> 1) << 2; - _oprLevelOut[(i << 1) + 1] = -_oprLevelOut[i << 1]; - for (int ii = 1; ii < 13; ++ii) { - _oprLevelOut[(i << 1) + (ii << 9)] = _oprLevelOut[i << 1] >> ii; - _oprLevelOut[(i << 1) + (ii << 9) + 1] = -_oprLevelOut[(i << 1) + (ii << 9)]; - } - } - - uint8 *dtt = new uint8[128]; - memset(dtt, 0, 36); - memset(dtt + 36, 1, 8); - memcpy(dtt + 44, _detSrc, 84); - - delete[] _oprDetune; - _oprDetune = new int32[256]; - for (int i = 0; i < 128; i++) { - _oprDetune[i] = (int32)((float)dtt[i] * _baserate * 64.0); - _oprDetune[i + 128] = -_oprDetune[i]; - } - - delete[] dtt; -} - -void TownsPC98_FmSynth::nextTick(int32 *buffer, uint32 bufferSize) { - if (!_ready) - return; - - for (int i = 0; i < _numChan; i++) { - TownsPC98_FmSynthOperator **o = _chanInternal[i].opr; - - if (_chanInternal[i].updateEnvelopeParameters) { - _chanInternal[i].updateEnvelopeParameters = false; - for (int ii = 0; ii < 4 ; ii++) - o[ii]->updatePhaseIncrement(); - } - - for (uint32 ii = 0; ii < bufferSize ; ii++) { - int32 phbuf1, phbuf2, output; - phbuf1 = phbuf2 = output = 0; - - int32 *leftSample = &buffer[ii * 2]; - int32 *rightSample = &buffer[ii * 2 + 1]; - int32 *del = &_chanInternal[i].feedbuf[2]; - int32 *feed = _chanInternal[i].feedbuf; - - switch (_chanInternal[i].algorithm) { - case 0: - o[0]->generateOutput(0, feed, phbuf1); - o[2]->generateOutput(*del, 0, phbuf2); - *del = 0; - o[1]->generateOutput(phbuf1, 0, *del); - o[3]->generateOutput(phbuf2, 0, output); - break; - case 1: - o[0]->generateOutput(0, feed, phbuf1); - o[2]->generateOutput(*del, 0, phbuf2); - o[1]->generateOutput(0, 0, phbuf1); - o[3]->generateOutput(phbuf2, 0, output); - *del = phbuf1; - break; - case 2: - o[0]->generateOutput(0, feed, phbuf2); - o[2]->generateOutput(*del, 0, phbuf2); - o[1]->generateOutput(0, 0, phbuf1); - o[3]->generateOutput(phbuf2, 0, output); - *del = phbuf1; - break; - case 3: - o[0]->generateOutput(0, feed, phbuf2); - o[2]->generateOutput(0, 0, *del); - o[1]->generateOutput(phbuf2, 0, phbuf1); - o[3]->generateOutput(*del, 0, output); - *del = phbuf1; - break; - case 4: - o[0]->generateOutput(0, feed, phbuf1); - o[2]->generateOutput(0, 0, phbuf2); - o[1]->generateOutput(phbuf1, 0, output); - o[3]->generateOutput(phbuf2, 0, output); - *del = 0; - break; - case 5: - o[0]->generateOutput(0, feed, phbuf1); - o[2]->generateOutput(*del, 0, output); - o[1]->generateOutput(phbuf1, 0, output); - o[3]->generateOutput(phbuf1, 0, output); - *del = phbuf1; - break; - case 6: - o[0]->generateOutput(0, feed, phbuf1); - o[2]->generateOutput(0, 0, output); - o[1]->generateOutput(phbuf1, 0, output); - o[3]->generateOutput(0, 0, output); - *del = 0; - break; - case 7: - o[0]->generateOutput(0, feed, output); - o[2]->generateOutput(0, 0, output); - o[1]->generateOutput(0, 0, output); - o[3]->generateOutput(0, 0, output); - *del = 0; - break; - }; - - int32 finOut = (output << 2) / ((_numChan + _numSSG - 3) / 3); - - if ((1 << i) & _volMaskA) - finOut = (finOut * _volumeA) / Audio::Mixer::kMaxMixerVolume; - - if ((1 << i) & _volMaskB) - finOut = (finOut * _volumeB) / Audio::Mixer::kMaxMixerVolume; - - if (_chanInternal[i].enableLeft) - *leftSample += finOut; - - if (_chanInternal[i].enableRight) - *rightSample += finOut; - } - } -} - -const uint32 TownsPC98_FmSynth::_adtStat[] = { - 0x00010001, 0x00010001, 0x00010001, 0x01010001, - 0x00010101, 0x00010101, 0x00010101, 0x01010101, - 0x01010101, 0x01010101, 0x01010102, 0x01010102, - 0x01020102, 0x01020102, 0x01020202, 0x01020202, - 0x02020202, 0x02020202, 0x02020204, 0x02020204, - 0x02040204, 0x02040204, 0x02040404, 0x02040404, - 0x04040404, 0x04040404, 0x04040408, 0x04040408, - 0x04080408, 0x04080408, 0x04080808, 0x04080808, - 0x08080808, 0x08080808, 0x10101010, 0x10101010 -}; - -const uint8 TownsPC98_FmSynth::_detSrc[] = { - 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, - 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, - 0x08, 0x08, 0x08, 0x08, 0x01, 0x01, 0x01, 0x01, - 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, - 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, - 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x10, 0x10, 0x10, 0x10, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, - 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, - 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x13, 0x14, - 0x16, 0x16, 0x16, 0x16 -}; - -const int TownsPC98_FmSynth::_ssgTables[] = { - 0x01202A, 0x0092D2, 0x006B42, 0x0053CB, 0x003DF8, 0x003053, 0x0022DA, 0x001A8C, - 0x00129B, 0x000DC1, 0x000963, 0x0006C9, 0x000463, 0x0002FA, 0x0001B6, 0x0000FB, - 0x0193B6, 0x01202A, 0x00CDB1, 0x0092D2, 0x007D7D, 0x006B42, 0x005ECD, 0x0053CB, - 0x00480F, 0x003DF8, 0x0036B9, 0x003053, 0x00290A, 0x0022DA, 0x001E6B, 0x001A8C, - 0x001639, 0x00129B, 0x000FFF, 0x000DC1, 0x000B5D, 0x000963, 0x0007FB, 0x0006C9, - 0x000575, 0x000463, 0x00039D, 0x0002FA, 0x000242, 0x0001B6, 0x00014C, 0x0000FB -}; - -#ifndef DISABLE_PC98_RHYTHM_CHANNEL -const uint8 TownsPC98_FmSynth::_percussionData[] = { - 0, 24, 1, 192, 1, 216, 2, 128, 4, 88, 23, 64, 27, 152, 1, 128, 29, 24, 2, 128, 31, 152, 0, 128, 136, 128, 128, 128, 0, 136, 97, 103, 153, 139, 34, 163, 72, 195, 27, 69, 1, 154, 137, 35, 8, 51, 169, 122, 164, 75, 133, 203, 81, 146, 168, 121, 185, 68, 202, 8, 33, 237, 49, 177, 12, 133, 140, 17, 160, 42, 161, 10, 0, 137, 176, 57, - 233, 41, 160, 136, 235, 65, 177, 137, 128, 26, 164, 28, 3, 157, 51, 137, 1, 152, 113, 161, 40, 146, 115, 192, 56, 5, 169, 66, 161, 56, 1, 50, 145, 59, 39, 168, 97, 1, 160, 57, 7, 153, 50, 153, 32, 2, 25, 129, 32, 20, 186, 66, 129, 24, 153, 164, 142, 130, 169, 153, 26, 242, 138, 217, 9, 128, 204, 58, 209, 172, 40, 176, 141, - 128, 155, 144, 203, 139, 0, 235, 9, 177, 172, 0, 185, 168, 138, 25, 240, 59, 211, 139, 19, 176, 90, 160, 17, 26, 132, 41, 1, 5, 25, 3, 50, 144, 115, 147, 42, 39, 152, 41, 3, 56, 193, 105, 130, 155, 66, 200, 26, 19, 218, 154, 49, 201, 171, 138, 176, 251, 139, 185, 172, 136, 189, 139, 145, 207, 41, 160, 171, 152, 186, 139, - 186, 141, 128, 218, 171, 51, 217, 170, 56, 163, 12, 4, 155, 81, 147, 42, 37, 152, 32, 54, 136, 49, 50, 48, 37, 32, 69, 0, 17, 50, 50, 83, 2, 16, 68, 20, 8, 66, 4, 154, 84, 145, 24, 33, 24, 32, 17, 18, 145, 32, 22, 168, 49, 163, 1, 33, 50, 184, 115, 129, 25, 66, 1, 24, 67, 2, 80, 35, 40, 53, 2, 65, 51, 19, 67, 37, 0, 52, 35, 49, 37, - 34, 49, 37, 17, 52, 17, 35, 35, 35, 34, 32, 49, 33, 152, 34, 145, 24, 24, 128, 138, 128, 184, 9, 177, 171, 168, 185, 155, 152, 172, 155, 186, 172, 185, 172, 155, 186, 173, 153, 202, 187, 185, 202, 170, 171, 202, 186, 169, 170, 170, 171, 139, 154, 171, 153, 154, 169, 10, 168, 154, 128, 168, 154, 0, 153, 152, 136, 137, - 128, 153, 0, 152, 8, 128, 137, 0, 136, 136, 8, 9, 8, 9, 8, 24, 153, 128, 136, 153, 144, 0, 161, 138, 1, 169, 136, 128, 160, 168, 152, 153, 138, 137, 154, 153, 153, 154, 153, 170, 168, 170, 185, 168, 169, 154, 169, 171, 153, 169, 170, 153, 152, 154, 153, 137, 169, 137, 136, 144, 152, 144, 128, 128, 144, 129, 129, 0, 33, - 0, 17, 17, 17, 33, 33, 18, 18, 34, 34, 34, 34, 34, 34, 35, 19, 35, 19, 35, 35, 18, 19, 18, 35, 18, 33, 0, 8, 8, 8, 8, 8, 8, 8, 160, 205, 65, 176, 171, 203, 16, 240, 95, 242, 120, 145, 156, 66, 177, 26, 19, 153, 9, 35, 35, 239, 56, 132, 138, 154, 50, 145, 203, 25, 32, 20, 237, 24, 130, 138, 160, 27, 39, 173, 50, 203, 64, 145, 139, - 18, 168, 48, 146, 171, 65, 18, 176, 12, 52, 128, 25, 5, 57, 240, 104, 161, 25, 129, 18, 188, 114, 160, 26, 36, 200, 154, 18, 1, 128, 186, 73, 162, 173, 32, 184, 25, 144, 137, 234, 8, 154, 32, 160, 158, 18, 187, 81, 2, 235, 41, 36, 144, 154, 17, 67, 128, 33, 160, 114, 146, 26, 37, 33, 232, 41, 130, 41, 178, 29, 50, 251, 24, - 1, 153, 138, 160, 76, 179, 155, 11, 0, 38, 252, 41, 146, 41, 178, 27, 193, 43, 39, 170, 136, 17, 129, 8, 49, 233, 48, 129, 11, 6, 26, 130, 136, 128, 64, 1, 248, 105, 145, 9, 16, 144, 140, 5, 25, 168, 16, 186, 48, 5, 171, 217, 57, 134, 171, 8, 34, 188, 20, 203, 41, 6, 155, 161, 89, 164, 140, 2, 136, 51, 202, 41, 131, 56, 144, - 8, 97, 144, 146, 13, 69, 200, 42, 130, 25, 152, 57, 6, 220, 88, 177, 26, 148, 9, 168, 8, 67, 192, 156, 65, 145, 137, 10, 4, 154, 18, 157, 67, 160, 154, 1, 50, 188, 82, 170, 82, 185, 49, 220, 97, 144, 10, 8, 16, 145, 9, 136, 18, 202, 51, 184, 141, 114, 179, 139, 24, 19, 8, 250, 121, 160, 40, 160, 10, 18, 152, 168, 42, 35, 216, - 187, 120, 145, 18, 156, 203, 84, 144, 9, 144, 26, 66, 161, 13, 1, 128, 17, 154, 18, 142, 6, 154, 65, 192, 29, 35, 186, 64, 192, 24, 9, 146, 56, 185, 16, 248, 121, 176, 40, 129, 136, 171, 96, 147, 140, 50, 203, 64, 144, 41, 128, 161, 187, 71, 200, 24, 129, 24, 217, 56, 20, 220, 24, 4, 169, 9, 1, 33, 201, 26, 134, 141, 51, 201, - 25, 16, 33, 235, 32, 144, 33, 153, 169, 99, 160, 11, 3, 136, 58, 210, 33, 203, 48, 163, 17, 219, 128, 140, 38, 8, 184, 141, 50, 131, 159, 33, 128, 153, 25, 18, 153, 88, 242, 43, 3, 9, 136, 157, 53, 202, 40, 145, 25, 2, 204, 105, 146, 156, 66, 152, 8, 153, 33, 128, 129, 136, 153, 50, 186, 55, 188, 51, 249, 64, 178, 27, 128, - 48, 177, 156, 18, 35, 175, 51, 189, 32, 51, 234, 155, 69, 184, 26, 2, 152, 9, 17, 136, 144, 137, 50, 235, 115, 216, 24, 2, 170, 67, 187, 49, 129, 155, 4, 27, 129, 56, 232, 43, 39, 203, 40, 3, 154, 169, 66, 184, 114, 224, 25, 2, 9, 128, 11, 35, 155, 18, 11, 202, 84, 169, 26, 5, 154, 8, 160, 98, 185, 17, 187, 50, 23, 188, 33, - 1, 139, 4, 154, 90, 147, 12, 3, 43, 2, 170, 171, 103, 193, 28, 132, 137, 8, 129, 24, 170, 50, 201, 42, 35, 202, 169, 52, 201, 33, 218, 40, 39, 203, 0, 40, 147, 29, 163, 139, 83, 185, 1, 4, 159, 34, 160, 12, 21, 155, 40, 129, 137, 58, 151, 13, 2, 136, 144, 16, 153, 40, 17, 131, 207, 51, 144, 140, 4, 154, 17, 146, 170, 73, 163, - 44, 164, 12, 152, 37, 203, 17, 128, 144, 139, 23, 154, 128, 138, 38, 216, 41, 1, 0, 233, 73, 131, 171, 49, 136, 9, 164, 46, 3, 171, 32, 0, 145, 157, 38, 187, 64, 176, 58, 134, 155, 18, 136, 217, 64, 1, 200, 140, 38, 153, 170, 66, 161, 8, 169, 65, 185, 98, 200, 41, 3, 155, 144, 58, 23, 187, 1, 145, 40, 147, 189, 32, 68, 249, - 1, 112, 255, 199, 195, 19, 108, 76, 187, 247, 247, 183, 40, 168, 212, 245, 199, 227, 68, 45, 59, 10, 145, 177, 198, 24, 130, 76, 26, 193, 180, 129, 0, 162, 42, 160, 199, 162, 0, 16, 152, 137, 132, 168, 195, 130, 162, 181, 227, 163, 161, 179, 211, 180, 179, 164, 128, 162, 161, 194, 164, 179, 40, 153, 195, 213, 146, 178, - 147, 176, 50, 186, 161, 196, 151, 58, 16, 28, 162, 160, 131, 122, 155, 33, 241, 146, 128, 40, 26, 128, 154, 36, 170, 89, 59, 9, 24, 144, 77, 161, 8, 177, 112, 139, 33, 232, 148, 24, 41, 61, 9, 26, 162, 32, 30, 58, 153, 32, 59, 73, 59, 11, 79, 137, 57, 9, 49, 30, 24, 153, 131, 25, 106, 61, 153, 73, 28, 56, 27, 41, 137, 148, - 76, 43, 74, 58, 13, 161, 3, 171, 149, 32, 77, 10, 74, 42, 168, 16, 0, 123, 138, 129, 162, 178, 225, 50, 140, 161, 0, 147, 10, 129, 41, 244, 210, 165, 1, 152, 24, 162, 184, 166, 32, 144, 59, 216, 132, 177, 8, 145, 67, 143, 146, 160, 183, 162, 130, 24, 192, 32, 225, 146, 144, 33, 44, 73, 30, 129, 137, 32, 76, 152, 25, 161, - 2, 154, 32, 177, 132, 232, 2, 136, 210, 128, 149, 177, 32, 58, 27, 168, 225, 133, 8, 44, 107, 136, 25, 136, 17, 26, 58, 46, 16, 11, 145, 17, 144, 79, 136, 144, 136, 145, 152, 33, 31, 162, 130, 200, 82, 153, 74, 137, 147, 26, 0, 13, 133, 170, 149, 16, 192, 0, 178, 0, 128, 152, 182, 150, 9, 16, 9, 137, 33, 59, 63, 10, 152, 32, - 179, 192, 5, 154, 228, 182, 145, 130, 144, 42, 128, 242, 2, 136, 41, 168, 17, 76, 57, 31, 129, 136, 17, 47, 8, 41, 138, 32, 138, 123, 59, 58, 10, 136, 161, 4, 46, 25, 145, 136, 129, 25, 56, 28, 91, 41, 154, 108, 9, 16, 44, 24, 137, 48, 15, 0, 194, 162, 41, 194, 56, 241, 163, 146, 0, 139, 7, 186, 150, 129, 152, 1, 208, 33, 176, - 136, 164, 163, 185, 7, 138, 130, 242, 162, 163, 177, 88, 136, 184, 166, 146, 0, 25, 25, 177, 199, 146, 16, 136, 9, 145, 178, 178, 0, 147, 138, 229, 18, 152, 25, 144, 163, 246, 162, 129, 129, 184, 5, 152, 178, 145, 148, 136, 146, 95, 152, 128, 144, 33, 170, 81, 11, 40, 202, 131, 0, 243, 24, 1, 11, 148, 42, 24, 163, 140, - 120, 9, 76, 58, 153, 145, 56, 30, 72, 46, 42, 9, 8, 57, 91, 76, 59, 26, 160, 129, 41, 76, 10, 57, 192, 163, 129, 16, 225, 2, 27, 40, 200, 48, 91, 226, 40, 145, 43, 177, 177, 182, 196, 145, 33, 184, 165, 17, 192, 163, 194, 129, 211, 128, 162, 197, 129, 0, 136, 211, 146, 8, 162, 144, 0, 167, 160, 1, 176, 150, 137, 1, 24, 243, - 0, 129, 145, 25, 123, 169, 130, 168, 132, 41, 63, 42, 136, 137, 120, 26, 136, 8, 24, 89, 29, 58, 177, 193, 147, 1, 26, 162, 176, 167, 180, 8, 49, 28, 29, 178, 162, 88, 43, 42, 57, 43, 61, 8, 29, 129, 128, 128, 123, 137, 24, 243, 16, 136, 16, 46, 0, 169, 149, 128, 1, 60, 153, 72, 154, 90, 25, 25, 25, 8, 91, 73, 12, 16, 137, 144, - 72, 11, 8, 167, 128, 129, 9, 138, 166, 193, 147, 162, 123, 137, 145, 1, 162, 26, 1, 219, 147, 129, 210, 147, 243, 1, 243, 16, 144, 145, 160, 131, 200, 4, 59, 75, 57, 218, 2, 178, 77, 24, 60, 11, 147, 10, 50, 141, 64, 27, 185, 122, 161, 41, 128, 90, 136, 24, 46, 16, 139, 16, 24, 28, 124, 9, 41, 8, 26, 121, 10, 42, 40, 139, 129, - 0, 201, 135, 137, 56, 176, 176, 35, 215, 145, 1, 26, 145, 144, 160, 135, 138, 1, 177, 146, 146, 161, 65, 242, 136, 164, 177, 1, 1, 186, 151, 208, 148, 129, 10, 32, 241, 145, 163, 178, 17, 168, 136, 151, 168, 2, 148, 185, 133, 176, 130, 129, 154, 163, 215, 0, 146, 136, 40, 211, 161, 131, 171, 81, 144, 170, 21, 184, 56, - 195, 168, 133, 177, 91, 16, 187, 5, 145, 153, 66, 172, 18, 177, 42, 120, 138, 27, 134, 26, 106, 42, 138, 146, 184, 66, 75, 46, 41, 168, 0, 145, 57, 91, 75, 27, 24, 27, 48, 169, 40, 122, 9, 109, 10, 8, 177, 146, 16, 74, 30, 129, 160, 162, 146, 41, 124, 138, 24, 145, 152, 3, 1, 14, 3, 139, 1, 192, 161, 151, 177, 122, 8, 10, 0, - 176, 130, 129, 27, 88, 225, 0, 2, 154, 129, 129, 193, 49, 203, 81, 153, 226, 33, 0, 30, 0, 176, 179, 18, 9, 96, 156, 162, 148, 160, 129, 2, 29, 195, 128, 0, 56, 156, 20, 232, 129, 128, 32, 10, 144, 74, 183, 9, 145, 162, 1, 162, 138, 23, 171, 1, 164, 224, 34, 43, 43, 177, 200, 135, 161, 91, 57, 154, 177, 148, 145, 146, 58, - 108, 136, 170, 35, 208, 177, 34, 128, 44, 129, 155, 151, 243, 16, 1, 154, 72, 193, 144, 18, 11, 122, 160, 153, 5, 192, 24, 130, 184, 132, 226, 0, 128, 153, 131, 181, 136, 65, 154, 128, 17, 170, 39, 28, 59, 144, 168, 80, 25, 47, 24, 26, 144, 32, 47, 41, 153, 161, 148, 8, 92, 9, 9, 129, 144, 33, 26, 47, 24, 137, 108, 25, 10, - 17, 10, 73, 75, 47, 24, 184, 48, 8, 45, 57, 138, 136, 150, 10, 48, 139, 136, 35, 203, 121, 8, 27, 179, 161, 106, 0, 29, 16, 176, 179, 3, 185, 19, 227, 41, 145, 168, 61, 197, 177, 20, 10, 57, 42, 250, 147, 196, 16, 41, 138, 24, 195, 208, 135, 137, 0, 145, 160, 2, 210, 146, 195, 177, 132, 136, 153, 167, 210, 146, 162, 40, 8, - 138, 148, 227, 145, 17, 137, 40, 169, 179, 130, 242, 2, 196, 9, 146, 145, 169, 167, 146, 130, 137, 136, 51, 220, 17, 163, 28, 74, 10, 76, 40, 140, 5, 137, 43, 18, 12, 107, 137, 40, 8, 201, 50, 0, 143, 3, 138, 161, 134, 138, 104, 169, 16, 162, 160, 121, 25, 28, 129, 152, 32, 56, 14, 16, 184, 146, 3, 46, 25, 176, 129, 179, - 193, 17, 130, 202, 135, 8, 57, 25, 154, 148, 184, 120, 9, 153, 211, 165, 24, 128, 26, 17, 242, 161, 18, 185, 81, 42, 11, 17, 12, 25, 181, 137, 66, 42, 47, 41, 184, 166, 129, 24, 91, 27, 136, 196, 0, 0, 74, 28, 178, 161, 149, 160, 32, 8, 225, 32, 128, 59, 8, 169, 50, 139, 47, 72, 186, 16, 132, 9, 122, 9, 160, 146, 144, 89, 153, - 10, 149, 178, 0, 121, 11, 146, 152, 162, 48, 13, 123, 177, 24, 0, 106, 27, 9, 144, 132, 12, 17, 0, 168, 0, 181, 56, 169, 129, 242, 195, 129, 17, 154, 64, 161, 244, 16, 137, 24, 144, 144, 164, 129, 75, 42, 176, 149, 9, 179, 148, 203, 4, 166, 136, 163, 128, 227, 163, 8, 57, 11, 30, 165, 0, 74, 59, 62, 9, 208, 131, 144, 40, 76, - 26, 27, 196, 129, 1, 25, 43, 49, 174, 67, 153, 136, 106, 152, 41, 25, 28, 2, 43, 44, 104, 45, 59, 8, 43, 128, 144, 120, 25, 12, 17, 152, 9, 130, 155, 151, 145, 74, 40, 13, 48, 192, 58, 90, 43, 43, 177, 146, 49, 31, 75, 24, 217, 131, 0, 76, 26, 152, 149, 161, 24, 74, 154, 193, 166, 145, 32, 27, 161, 164, 176, 135, 152, 24, 193, - 162, 146, 164, 58, 227, 193, 148, 161, 128, 18, 234, 130, 180, 145, 2, 200, 1, 163, 186, 98, 184, 129, 149, 153, 49, 42, 186, 151, 242, 129, 1, 43, 8, 177, 212, 165, 8, 40, 137, 24, 8, 144, 90, 9, 25, 48, 44, 46, 24, 138, 40, 144, 108, 58, 27, 128, 181, 128, 80, 29, 42, 152, 162, 130, 25, 106, 136, 11, 148, 8, 144, 128, 136, - 112, 139, 80, 153, 24, 136, 129, 46, 0, 60, 129, 208, 1, 3, 13, 57, 168, 144, 1, 242, 17, 9, 26, 2, 185, 27, 55, 140, 73, 137, 179, 16, 192, 3, 145, 143, 33, 9, 171, 135, 160, 17, 137, 10, 151, 168, 3, 178, 44, 17, 208, 144, 167, 0, 40, 155, 16, 167, 152, 18, 144, 26, 160, 199, 1, 136, 91, 136, 160, 178, 150, 161, 1, 10, 181, - 145, 161, 1, 145, 161, 198, 2, 9, 90, 137, 177, 160, 150, 40, 29, 129, 144, 145, 162, 57, 77, 169, 16, 148, 42, 42, 40, 141, 34, 170, 121, 154, 210, 131, 162, 107, 8, 9, 160, 195, 40, 73, 139, 18, 224, 162, 34, 139, 0, 244, 178, 163, 24, 26, 146, 194, 166, 49, 29, 42, 137, 130, 192, 16, 93, 128, 154, 19, 59, 11, 122, 11, - 146, 177, 120, 42, 26, 43, 164, 152, 17, 60, 63, 137, 128, 48, 10, 58, 92, 9, 59, 91, 75, 139, 32, 25, 25, 61, 74, 28, 177, 40, 130, 74, 29, 73, 168, 130, 128, 48, 14, 8, 77, 9, 25, 26, 179, 211, 32, 78, 26, 41, 152, 161, 180, 89, 59, 9, 153, 166, 160, 3, 26, 57, 106, 154, 88, 184, 40, 1, 27, 58, 73, 143, 131, 169, 3, 161, 184, - 122, 152, 16, 181, 145, 129, 17, 15, 129, 193, 147, 145, 192, 33, 193, 162, 183, 163, 136, 178, 129, 178, 197, 2, 41, 216, 131, 168, 163, 181, 226, 163, 178, 1, 33, 187, 166, 212, 129, 1, 27, 24, 162, 184, 151, 8, 16, 160, 144, 181, 210, 72, 168, 128, 32, 42, 25, 40, 142, 5, 185, 88, 58, 11, 58, 177, 32, 129, 63, 42, 136, - 186, 53, 29, 75, 58, 144, 144, 129, 77, 128, 11, 144, 133, 29, 40, 152, 24, 161, 129, 80, 155, 60, 3, 12, 89, 8, 60, 152, 152, 49, 136, 47, 57, 224, 129, 16, 41, 90, 139, 162, 147, 170, 51, 169, 27, 17, 95, 26, 26, 160, 5, 139, 48, 76, 10, 228, 146, 1, 136, 44, 161, 147, 209, 130, 137, 73, 224, 1, 162, 195, 32, 210, 177, 180, - 179, 148, 145, 154, 132, 242, 146, 1, 152, 32, 192, 1, 144, 155, 7, 177, 168, 5, 138, 178, 148, 152, 150, 136, 89, 152, 9, 41, 196, 145, 40, 28, 16, 8, 10, 178, 167, 24, 1, 44, 123, 137, 136, 145, 194, 48, 27, 74, 26, 192, 179, 135, 136, 88, 27, 10, 177, 163, 164, 128, 73, 24, 31, 8, 0, 192, 149, 144, 129, 9, 106, 41, 200, - 161, 151, 41, 138, 0, 24, 226, 162, 49, 42, 11, 90, 136, 136, 152, 17, 145, 10, 63, 40, 11, 56, 245, 162, 16, 26, 73, 11, 144, 135, 137, 58, 106, 10, 25, 8, 57, 137, 28, 33, 129, 156, 113, 10, 10, 161, 18, 8, 153, 77, 3, 217, 0, 1, 242, 128, 193, 18, 128, 75, 60, 178, 154, 37, 45, 58, 29, 144, 1, 184, 66, 41, 29, 8, 145, 10, - 194, 33, 148, 170, 107, 89, 139, 128, 163, 178, 16, 63, 59, 176, 144, 151, 129, 42, 74, 10, 129, 192, 2, 128, 154, 97, 192, 0, 177, 128, 178, 183, 16, 16, 155, 149, 145, 184, 84, 138, 8, 192, 161, 20, 225, 0, 130, 138, 165, 0, 28, 148, 153, 18, 209, 128, 88, 153, 89, 152, 9, 17, 9, 29, 130, 43, 122, 153, 24, 32, 202, 49, - 24, 43, 106, 154, 130, 193, 27, 51, 29, 28, 133, 138, 65, 11, 123, 25, 10, 40, 152, 44, 130, 26, 43, 148, 45, 73, 140, 33, 8, 153, 88, 128, 61, 144, 42, 59, 225, 128, 18, 155, 50, 75, 186, 20, 202, 120, 144, 42, 92, 176, 162, 165, 25, 2, 169, 152, 135, 185, 19, 152, 8, 146, 160, 123, 195, 137, 132, 209, 0, 16, 11, 2, 242, - 146, 164, 152, 73, 193, 136, 130, 178, 1, 136, 169, 23, 169, 128, 164, 242, 129, 178, 129, 32, 138, 180, 167, 153, 132, 8, 138, 2, 209, 4, 138, 1, 128, 138, 92, 136, 44, 129, 136, 162, 33, 63, 40, 141, 2, 160, 144, 106, 137, 64, 155, 17, 129, 60, 30, 146, 26, 17, 28, 48, 46, 169, 51, 154, 91, 137, 41, 26, 32, 143, 18, 138, - 1, 32, 28, 123, 177, 9, 181, 195, 56, 57, 14, 145, 161, 17, 17, 31, 41, 152, 145, 194, 194, 20, 153, 41, 9, 243, 129, 180, 0, 128, 45, 16, 43, 170, 135, 144, 16, 25, 42, 137, 242, 163, 194, 16, 0, 57, 14, 130, 194, 178, 16, 33, 30, 8, 59, 211, 163, 160, 5, 137, 44, 10, 17, 170, 3, 120, 9, 44, 146, 136, 131, 140, 91, 9, 171, - 7, 161, 32, 73, 13, 8, 161, 40, 106, 11, 25, 129, 59, 0, 49, 31, 42, 28, 40, 11, 0, 81, 176, 61, 32, 138, 25, 178, 241, 148, 136, 106, 8, 136, 128, 177, 90, 8, 155, 96, 176, 9, 18, 217, 132, 129, 10, 81, 156, 40, 178, 161, 36, 169, 76, 147, 203, 150, 0, 10, 146, 200, 147, 149, 128, 144, 148, 154, 182, 24, 0, 137, 11, 134, 211, - 24, 136, 129, 145, 209, 33, 8, 43, 163, 243, 88, 41, 13, 0, 160, 145, 33, 31, 32, 185, 145, 4, 155, 17, 32, 47, 161, 128, 73, 160, 44, 56, 176, 75, 74, 12, 35, 141, 104, 137, 9, 89, 152, 58, 56, 44, 41, 30, 41, 40, 157, 48, 128, 154, 88, 41, 42, 8, 14, 3, 184, 59, 120, 152, 9, 56, 10, 128, 41, 57, 227, 186, 52, 152, 62, 8, 56, - 242, 0, 58, 8, 156, 34, 243, 128, 24, 176, 51, 169, 58, 183, 192, 146, 164, 177, 18, 170, 7, 177, 208, 132, 161, 24, 136, 27, 147, 243, 128, 133, 10, 24, 161, 161, 178, 214, 17, 160, 25, 16, 161, 137, 165, 192, 48, 27, 72, 58, 218, 133, 162, 26, 72, 27, 10, 197, 178, 49, 138, 89, 56, 142, 1, 24, 11, 0, 44, 105, 10, 25, 0, - 194, 9, 3, 47, 8, 138, 147, 18, 28, 48, 202, 147, 199, 146, 25, 161, 0, 145, 194, 163, 57, 11, 146, 248, 130, 32, 57, 63, 154, 16, 48, 14, 128, 144, 209, 133, 26, 56, 154, 182, 162, 195, 18, 152, 44, 194, 180, 168, 5, 24, 137, 138, 35, 192, 232, 66, 176, 161, 24, 41, 26, 244, 129, 163, 160, 75, 129, 226, 147, 40, 145, 61, - 13, 130, 177, 17, 137, 112, 170, 130, 0, 136, 75, 152, 177, 241, 34, 0, 59, 156, 51, 186, 178, 91, 132, 137, 137, 122, 1, 45, 28, 50, 172, 57, 108, 8, 26, 136, 32, 152, 46, 144, 131, 171, 4, 152, 18, 141, 148, 1, 216, 32, 9, 60, 169, 66, 152, 128, 72, 90, 201, 1, 17, 201, 136, 3, 195, 26, 73, 133, 200, 176, 150, 146, 169, - 24, 33, 178, 184, 151, 73, 11, 28, 72, 44, 153, 82, 153, 17, 42, 57, 78, 153, 8, 160, 0, 1, 123, 11, 19, 171, 195, 18, 59, 31, 129, 10, 162, 2, 58, 96, 142, 130, 26, 75, 128, 176, 17, 180, 123, 9, 90, 137, 211, 145, 32, 26, 76, 43, 145, 130, 12, 90, 41, 27, 58, 160, 160, 128, 178, 7, 76, 59, 0, 203, 180, 147, 33, 62, 10, 0, 243, - 129, 146, 73, 29, 145, 144, 0, 26, 56, 153, 185, 83, 8, 76, 27, 166, 161, 193, 146, 131, 224, 145, 165, 161, 40, 168, 149, 162, 226, 2, 136, 138, 163, 131, 211, 0, 59, 146, 218, 148, 1, 192, 16, 16, 58, 248, 88, 144, 177, 136, 1, 58, 45, 9, 195, 197, 147, 48, 29, 10, 0, 162, 176, 64, 122, 9, 10, 17, 9, 153, 56, 75, 27, 31, - 72, 136, 9, 129, 129, 61, 45, 59, 10, 161, 18, 122, 43, 59, 41, 169, 34, 155, 130, 131, 219, 120, 162, 27, 49, 208, 160, 131, 156, 66, 12, 145, 50, 240, 16, 136, 12, 162, 40, 129, 130, 15, 129, 162, 146, 180, 83, 139, 58, 217, 129, 177, 4, 0, 169, 197, 163, 144, 242, 131, 168, 179, 179, 17, 197, 145, 178, 164, 128, 160, - 211, 2, 244, 163, 145, 162, 129, 212, 177, 163, 17, 208, 163, 195, 180, 57, 24, 170, 182, 164, 129, 0, 60, 60, 169, 149, 162, 177, 122, 26, 24, 136, 136, 133, 43, 27, 178, 56, 77, 24, 128, 240, 0, 2, 44, 46, 8, 128, 193, 146, 64, 27, 42, 16, 193, 25, 0, 192, 148, 11, 52, 47, 153, 147, 243, 0, 24, 73, 28, 144, 161, 150, 9, - 8, 73, 170, 2, 162, 25, 27, 147, 167, 131, 29, 1, 168, 200, 165, 16, 91, 137, 8, 162, 176, 35, 41, 31, 24, 169, 50, 168, 58, 123, 144, 48, 128, 13, 73, 169, 144, 16, 57, 123, 44, 200, 163, 56, 153, 80, 10, 176, 146, 57, 94, 8, 152, 131, 9, 168, 125, 26, 145, 177, 132, 137, 41, 60, 26, 144, 243, 32, 192, 34, 60, 43, 26, 16, - 249, 164, 16, 58, 61, 11, 130, 243, 146, 2, 42, 44, 27, 128, 165, 137, 49, 45, 28, 16, 43, 8, 211, 48, 28, 152, 105, 9, 9, 163, 161, 169, 35, 107, 42, 232, 164, 130, 168, 72, 42, 168, 210, 148, 144, 136, 129, 3, 217, 194, 50, 27, 192, 41, 210, 147, 40, 76, 226, 1, 161, 1, 155, 132, 145, 147, 171, 67, 173, 210, 132, 161, 106, - 137, 56, 169, 209, 131, 64, 13, 129, 9, 194, 17, 57, 61, 169, 17, 128, 40, 31, 16, 10, 162, 57, 61, 75, 139, 40, 242, 17, 58, 59, 138, 179, 144, 50, 105, 140, 179, 243, 57, 40, 26, 9, 243, 130, 24, 29, 57, 128, 210, 129, 25, 59, 91, 137, 162, 178, 72, 27, 181, 168, 19, 129, 8, 184, 231, 147, 178, 32, 28, 184, 198, 148, 144, - 1, 26, 128, 16, 192, 2, 26, 144, 244, 129, 0, 16, 10, 197, 177, 181, 1, 41, 9, 178, 165, 211, 129, 25, 145, 137, 210, 147, 152, 210, 163, 132, 194, 17, 91, 169, 145, 181, 130, 9, 89, 137, 152, 178, 4, 128, 9, 63, 160, 128, 106, 8, 25, 43, 10, 32, 47, 26, 123, 152, 24, 40, 25, 27, 18, 186, 35, 158, 64, 42, 216, 33, 25, 58, 58, - 45, 184, 147, 29, 72, 46, 9, 0, 178, 146, 58, 77, 26, 25, 209, 165, 128, 145, 17, 153, 128, 129, 148, 240, 129, 1, 40, 31, 0, 152, 242, 163, 16, 59, 44, 24, 243, 146, 128, 1, 26, 26, 179, 213, 145, 130, 176, 131, 40, 25, 145, 219, 179, 167, 8, 33, 59, 14, 176, 166, 16, 136, 74, 128, 176, 128, 149, 8, 8, 209, 148, 152, 0, 72, - 153, 161, 178, 35, 62, 75, 154, 163, 153, 19, 62, 170, 133, 179, 136, 89, 12, 129, 164, 144, 3, 47, 58, 193, 177, 148, 0, 61, 43, 10, 129, 17, 41, 61, 43, 25, 8, 126, 26, 25, 137, 145, 34, 44, 45, 129, 216, 179, 1, 90, 25, 137, 32, 227, 8, 16, 9, 170, 49, 31, 32, 29, 128, 145, 148, 75, 25, 75, 153, 162, 192, 35, 12, 80, 136, - 176, 8, 194, 24, 1, 176, 21, 154, 145, 80, 251, 130, 2, 30, 9, 8, 130, 145, 128, 98, 27, 26, 129, 136, 162, 15, 33, 168, 59, 65, 177, 77, 141, 1, 128, 168, 113, 10, 137, 178, 163, 146, 132, 74, 153, 224, 164, 33, 184, 19, 184, 228, 161, 17, 91, 152, 25, 146, 152, 44, 121, 9, 160, 145, 17, 25, 28, 93, 128, 152, 2, 25, 27, 161, - 210, 129, 146, 45, 179, 227, 163, 162, 9, 40, 193, 148, 179, 57, 107, 140, 196, 32, 25, 57, 47, 136, 210, 130, 24, 40, 28, 152, 210, 182, 145, 40, 8, 129, 184, 147, 147, 140, 163, 166, 160, 34, 45, 144, 194, 161, 134, 41, 46, 152, 162, 162, 3, 44, 58, 75, 209, 162, 144, 57, 129, 47, 152, 130, 59, 16, 248, 129, 17, 26, 57, - 9, 29, 167, 2, 60, 42, 138, 136, 209, 130, 90, 42, 42, 176, 146, 178, 120, 28, 8, 160, 145, 16, 33, 31, 1, 8, 160, 129, 128, 242, 164, 32, 152, 177, 146, 213, 196, 128, 40, 26, 160, 163, 180, 146, 108, 60, 144, 144, 136, 147, 137, 40, 90, 161, 3, 17, 219, 243, 33, 184, 130, 60, 136, 243, 178, 179, 132, 26, 8, 168, 212, 147, - 16, 57, 42, 31, 145, 145, 160, 32, 43, 184, 66, 45, 180, 33, 140, 226, 1, 91, 152, 16, 144, 193, 162, 48, 77, 25, 137, 153, 17, 178, 78, 0, 0, 16, 14, 90, 152, 153, 19, 129, 13, 123, 137, 129, 160, 1, 73, 44, 9, 129, 0, 153, 120, 10, 9, 162, 195, 32, 139, 28, 151, 161, 2, 128, 26, 45, 193, 146, 48, 29, 146, 153, 194, 5, 59, - 29, 128, 144, 195, 1, 64, 43, 208, 178, 149, 8, 9, 16, 240, 163, 129, 16, 42, 185, 181, 211, 24, 48, 45, 137, 149, 9, 24, 41, 75, 184, 177, 4, 43, 91, 128, 180, 16, 144, 29, 25, 184, 167, 1, 59, 60, 153, 148, 161, 146, 91, 42, 186, 4, 24, 145, 123, 11, 2, 178, 77, 136, 26, 25, 195, 40, 115, 61, 27, 168, 177, 3, 59, 79, 26, 25, - 144, 1, 48, 13, 56, 154, 248, 1, 16, 9, 129, 8, 2, 178, 31, 130, 153, 162, 20, 15, 33, 170, 56, 40, 29, 28, 128, 152, 149, 144, 56, 120, 11, 162, 212, 129, 144, 145, 59, 180, 243, 147, 145, 144, 16, 152, 48, 241, 0, 161, 176, 1, 134, 10, 129, 200, 166, 144, 128, 121, 26, 24, 177, 178, 196, 48, 75, 138, 41, 180, 195, 26, 24, - 89, 138, 24, 33, 187, 41, 84, 155, 57, 79, 136, 160, 210, 130, 0, 58, 58, 168, 243, 132, 27, 41, 75, 138, 3, 8, 61, 8, 29, 145, 179, 76, 24, 28, 146, 208, 2, 49, 140, 75, 196, 144, 0, 40, 44, 179, 208, 3, 176, 33, 15, 177, 2, 160, 106, 8, 160, 164, 164, 8, 73, 27, 226, 179, 161, 1, 57, 1, 196, 211, 128, 40, 156, 145, 166, 178, - 131, 29, 128, 145, 162, 165, 40, 27, 216, 146, 135, 144, 40, 160, 194, 177, 145, 20, 139, 200, 151, 178, 17, 136, 40, 25, 205, 130, 17, 11, 17, 129, 156, 38, 26, 25, 137, 179, 163, 11, 79, 16, 12, 146, 147, 143, 89, 25, 136, 136, 25, 48, 26, 46, 129, 40, 29, 42, 29, 8, 145, 2, 56, 27, 62, 8, 25, 212, 161, 48, 43, 144, 129, - 29, 145, 144, 41, 106, 10, 107, 43, 184, 131, 1, 36, 61, 13, 138, 2, 194, 1, 16, 27, 75, 186, 181, 151, 8, 1, 161, 138, 211, 129, 2, 59, 248, 129, 16, 0, 144, 63, 152, 150, 136, 24, 25, 128, 30, 161, 128, 17, 24, 225, 146, 10, 16, 0, 9, 227, 183, 129, 40, 60, 26, 162, 194, 181, 24, 90, 9, 24, 0, 176, 161, 193, 194, 35, 12, 63, - 8, 210, 162, 1, 32, 78, 28, 152, 164, 144, 16, 48, 45, 137, 162, 147, 168, 152, 98, 27, 43, 33, 12, 160, 165, 129, 137, 63, 41, 153, 153, 151, 16, 91, 26, 8, 8, 9, 56, 10, 46, 24, 146, 57, 168, 160, 166, 241, 129, 32, 140, 16, 145, 179, 164, 137, 113, 138, 208, 131, 26, 25, 1, 42, 178, 196, 106, 24, 171, 18, 196, 8, 18, 29, - 41, 194, 128, 3, 249, 57, 162, 152, 48, 184, 120, 160, 208, 33, 137, 74, 57, 187, 149, 129, 26, 35, 158, 72, 128, 168, 32, 26, 25, 180, 75, 2, 136, 15, 163, 161, 136, 120, 27, 41, 160, 128, 182, 56, 60, 25, 12, 178, 151, 128, 168, 72, 10, 152, 4, 177, 26, 147, 137, 113, 44, 42, 33, 220, 2, 152, 41, 82, 11, 210, 163, 184, - 133, 162, 10, 196, 128, 3, 234, 40, 149, 152, 161, 1, 44, 129, 194, 4, 225, 16, 58, 168, 24, 194, 146, 146, 154, 49, 21, 218, 33, 152, 248, 129, 194, 147, 0, 28, 1, 195, 162, 20, 140, 42, 25, 160, 198, 1, 33, 136, 142, 3, 25, 24, 141, 16, 177, 208, 112, 0, 138, 41, 160, 130, 45, 60, 32, 170, 73, 24, 75, 59, 161, 176, 49, 159, - 97, 26, 168, 149, 145, 32, 28, 25, 184, 211, 129, 179, 74, 73, 8, 153, 136, 193, 151, 160, 32, 48, 143, 9, 147, 181, 145, 32, 60, 9, 187, 133, 166, 144, 32, 152, 25, 136, 161, 150, 168, 145, 81, 10, 42, 0, 169, 182, 148, 136, 58, 41, 187, 182, 211, 131, 16, 137, 25, 243, 144, 129, 2, 9, 8, 202, 7, 25, 185, 21, 144, 136, 153, - 65, 184, 137, 56, 151, 10, 153, 49, 16, 145, 14, 56, 176, 11, 192, 19, 89, 91, 44, 168, 147, 2, 8, 147, 63, 27, 1, 136, 229, 129, 73, 26, 136, 26, 137, 81, 170, 147, 77, 72, 12, 42, 42, 192, 24, 104, 91, 26, 27, 65, 177, 27, 32, 41, 60, 14, 136, 17, 170, 150, 129, 24, 58, 11, 16, 251, 162, 19, 57, 31, 0, 152, 129, 145, 17, 61, - 14, 1, 129, 27, 129, 66, 169, 178, 74, 12, 11, 19, 198, 145, 75, 33, 138, 174, 133, 1, 184, 57, 40, 136, 169, 20, 1, 60, 174, 20, 154, 201, 67, 26, 162, 151, 42, 16, 138, 59, 130, 204, 20, 169, 59, 180, 59, 114, 184, 56, 178, 242, 128, 130, 43, 8, 194, 3, 229, 144, 33, 185, 144, 34, 181, 145, 168, 17, 149, 153, 74, 35, 220, - 129, 128, 1, 88, 59, 75, 225, 136, 130, 168, 17, 144, 12, 151, 8, 25, 179, 8, 1, 240, 16, 8, 25, 145, 211, 41, 130, 138, 115, 169, 160, 163, 168, 84, 154, 74, 0, 170, 144, 211, 149, 2, 30, 128, 137, 9, 149, 1, 144, 58, 60, 57, 153, 178, 150, 17, 29, 27, 74, 25, 195, 152, 56, 15, 1, 25, 26, 152, 149, 80, 153, 57, 73, 140, 128, - 160, 144, 113, 27, 56, 28, 25, 4, 42, 44, 137, 60, 171, 130, 50, 240, 8, 5, 139, 145, 1, 105, 137, 200, 80, 137, 145, 146, 178, 179, 160, 46, 16, 240, 195, 131, 128, 144, 24, 164, 198, 128, 0, 136, 137, 131, 194, 165, 177, 2, 161, 147, 11, 144, 188, 181, 148, 144, 23, 0, 28, 224, 128, 131, 192, 32, 1, 224, 1, 168, 132, 145, - 9, 41, 208, 58, 137, 179, 151, 145, 16, 1, 30, 8, 145, 178, 1, 47, 32, 186, 72, 169, 146, 75, 8, 41, 48, 136, 89, 13, 48, 9, 10, 124, 26, 11, 42, 32, 129, 91, 77, 16, 12, 128, 42, 57, 138, 10, 60, 2, 63, 9, 0, 93, 128, 152, 90, 8, 10, 24, 40, 44, 144, 29, 49, 188, 48, 72, 25, 30, 177, 33, 128, 186, 120, 129, 186, 133, 152, 130, - 24, 156, 51, 154, 8, 226, 2, 56, 155, 2, 179, 233, 167, 128, 24, 129, 176, 136, 151, 8, 184, 0, 33, 224, 152, 21, 177, 24, 10, 163, 16, 250, 17, 130, 171, 83, 137, 136, 37, 12, 56, 242, 154, 17, 160, 145, 82, 13, 3, 201, 128, 18, 137, 24, 162, 63, 162, 8, 107, 178, 128, 57, 158, 32, 24, 200, 18, 0, 106, 154, 73, 16, 248, 8, - 73, 137, 57, 75, 0, 128, 12, 65, 137, 59, 75, 28, 144, 129, 122, 0, 58, 140, 160, 195, 145, 105, 56, 28, 153, 145, 164, 88, 8, 28, 25, 153, 9, 162, 113, 89, 153, 136, 33, 234, 147, 128, 41, 72, 11, 138, 151, 144, 145, 16, 43, 58, 248, 130, 178, 42, 4, 40, 10, 196, 154, 147, 216, 24, 7, 136, 10, 161, 148, 210, 161, 98, 138, - 137, 128, 146, 176, 33, 105, 27, 43, 163, 49, 185, 6, 10, 136, 43, 67, 174, 161, 162, 151, 137, 1, 64, 200, 193, 24, 64, 200, 56, 145, 242, 24, 57, 137, 1, 128, 3, 162, 175, 80, 128, 162, 152, 25, 58, 175, 17, 17, 0, 200, 64, 168, 162, 91, 1, 154, 44, 211, 177, 35, 64, 160, 161, 144, 4, 241, 41, 209, 162, 25, 1, 3, 242, 176, - 134, 153, 42, 41, 136, 135, 154, 2, 130, 46, 41, 161, 153, 180, 145, 34, 26, 46, 18, 242, 137, 146, 129, 25, 128, 11, 151, 161, 40, 179, 27, 122, 168, 59, 137, 181, 50, 172, 36, 56, 15, 9, 129, 137, 128, 75, 2, 58, 12, 52, 141, 8, 24, 58, 153, 157, 122, 145, 9, 1, 80, 27, 184, 32, 74, 219, 50, 57, 168, 153, 180, 48, 28, 143, - 131, 144, 178, 65, 13, 48, 168, 162, 147, 155, 121, 9, 170, 5, 16, 153, 21, 29, 144, 161, 91, 0, 184, 57, 128, 137, 17, 159, 88, 178, 128, 105, 152, 9, 162, 33, 164, 141, 88, 178, 224, 1, 0, 16, 27, 185, 150, 161, 9, 4, 139, 16, 128, 160, 194, 144, 65, 180, 46, 40, 136, 27, 135, 160, 16, 44, 57, 145, 236, 2, 195, 40, 75, 177, - 2, 200, 179, 146, 186, 104, 50, 141, 24, 169, 165, 148, 11, 97, 10, 11, 130, 177, 49, 57, 78, 42, 154, 128, 165, 59, 33, 28, 30, 1, 136, 16, 192, 41, 128, 152, 123, 136, 24, 1, 169, 113, 10, 11, 49, 153, 14, 147, 19, 45, 43, 8, 176, 210, 148, 8, 16, 11, 96, 144, 192, 163, 150, 10, 128, 43, 26, 150, 178, 165, 24, 41, 171, 18, - 27, 215, 1, 8, 128, 136, 40, 35, 208, 11, 161, 193, 18, 73, 154, 133, 155, 165, 164, 10, 49, 154, 8, 199, 0, 2, 168, 64, 192, 0, 40, 162, 43, 202, 180, 150, 10, 106, 24, 185, 145, 131, 184, 113, 43, 24, 162, 187, 73, 146, 42, 81, 171, 121, 58, 155, 151, 16, 43, 32, 31, 9, 160, 146, 17, 136, 94, 10, 24, 145, 25, 9, 130, 59, - 65, 13, 91, 25, 169, 146, 176, 112, 42, 59, 16, 217, 130, 20, 13, 25, 9, 40, 161, 138, 68, 169, 154, 18, 62, 154, 180, 145, 135, 152, 56, 58, 155, 165, 211, 8, 40, 42, 10, 198, 1, 2, 184, 57, 184, 224, 51, 154, 27, 134, 168, 19, 202, 73, 75, 184, 35, 176, 75, 24, 25, 209, 51, 157, 19, 30, 184, 179, 3, 33, 148, 45, 232, 146, - 129, 168, 41, 32, 170, 149, 193, 35, 136, 16, 50, 191, 56, 146, 173, 149, 16, 24, 41, 30, 129, 168, 209, 3, 57, 31, 0, 16, 176, 147, 41, 152, 10, 17, 181, 14, 40, 144, 49, 170, 75, 97, 141, 25, 162, 146, 72, 177, 92, 137, 137, 19, 137, 153, 113, 154, 2, 41, 60, 129, 217, 2, 211, 152, 73, 42, 193, 197, 146, 147, 10, 59, 0, - 192, 196, 132, 41, 160, 25, 88, 169, 16, 40, 241, 1, 153, 81, 28, 10, 147, 161, 209, 88, 75, 9, 161, 162, 180, 16, 43, 57, 235, 33, 56, 156, 129, 144, 2, 135, 31, 128, 145, 136, 163, 56, 59, 154, 57, 167, 160, 105, 137, 0, 138, 163, 3, 41, 47, 185, 211, 131, 41, 41, 60, 139, 182, 146, 16, 16, 43, 242, 144, 145, 129, 16, 179, - 183, 1, 26, 9, 147, 240, 131, 160, 91, 74, 152, 184, 166, 178, 33, 140, 9, 4, 162, 233, 34, 136, 129, 144, 163, 60, 142, 144, 149, 128, 33, 73, 13, 161, 194, 131, 0, 26, 56, 142, 128, 163, 128, 1, 233, 56, 209, 41, 145, 194, 147, 179, 149, 64, 30, 8, 128, 216, 18, 24, 43, 43, 32, 153, 25, 74, 109, 137, 153, 48, 8, 137, 122, - 25, 144, 26, 43, 59, 30, 33, 41, 27, 24, 96, 153, 160, 50, 76, 27, 47, 152, 145, 163, 73, 40, 14, 152, 131, 176, 74, 90, 8, 8, 200, 67, 155, 154, 50, 49, 155, 28, 124, 177, 152, 1, 2, 17, 62, 138, 180, 176, 4, 25, 9, 177, 245, 162, 129, 40, 25, 176, 164, 130, 172, 4, 8, 181, 194, 49, 11, 168, 154, 165, 133, 152, 40, 136, 226, - 179, 19, 26, 185, 16, 167, 194, 16, 25, 57, 243, 136, 147, 1, 31, 25, 184, 132, 160, 33, 62, 138, 129, 130, 41, 121, 137, 153, 145, 26, 17, 107, 136, 179, 1, 61, 60, 26, 162, 168, 148, 64, 31, 25, 32, 168, 152, 64, 31, 137, 8, 129, 33, 62, 24, 137, 8, 16, 59, 47, 153, 33, 162, 91, 59, 41, 170, 145, 5, 43, 60, 41, 13, 178, 134, - 57, 153, 12, 194, 227, 8, 2, 128, 57, 208, 162, 19, 216, 32, 178, 25, 128, 160, 48, 194, 195, 37, 155, 10, 33, 251, 163, 146, 16, 136, 12, 166, 195, 160, 148, 129, 176, 147, 178, 150, 160, 72, 162, 162, 193, 162, 60, 200, 145, 5, 144, 25, 122, 216, 129, 161, 130, 0, 10, 73, 1, 241, 2, 9, 168, 33, 13, 161, 165, 24, 64, 203, - 50, 1, 14, 9, 9, 129, 161, 106, 33, 27, 13, 164, 128, 40, 41, 107, 169, 160, 33, 136, 60, 92, 168, 152, 2, 91, 57, 176, 129, 0, 144, 47, 136, 162, 164, 128, 80, 43, 154, 179, 213, 130, 74, 27, 0, 145, 145, 167, 58, 59, 160, 9, 26, 76, 8, 171, 5, 49, 28, 44, 169, 162, 183, 130, 72, 28, 144, 179, 228, 2, 25, 26, 129, 186, 151, - 1, 75, 128, 169, 17, 178, 15, 57, 170, 16, 166, 16, 57, 8, 139, 162, 181, 1, 8, 152, 164, 181, 41, 81, 43, 10, 242, 145, 57, 139, 89, 8, 193, 18, 154, 32, 176, 10, 165, 129, 137, 147, 177, 134, 0, 25, 25, 201, 147, 227, 129, 72, 59, 185, 167, 128, 129, 160, 91, 25, 176, 130, 147, 145, 9, 160, 5, 202, 17, 16, 186, 136, 37, - 177, 56, 76, 42, 169, 186, 48, 9, 145, 57, 24, 128, 41, 169, 134, 137, 145, 147, 28, 41, 168, 131, 228, 32, 27, 9, 60, 129, 178, 64, 60, 45, 25, 9, 24, 152, 49, 31, 136, 57, 42, 0, 25, 12, 181, 18, 153, 57, 96, 169, 177, 132, 153, 123, 9, 152, 129, 177, 17, 74, 43, 24, 169, 128, 121, 137, 25, 1, 139, 96, 42, 10, 146, 178, 18, - 44, 29, 1, 161, 164, 146, 31, 137, 146, 177, 19, 1, 10, 26, 209, 165, 146, 43, 40, 138, 240, 130, 18, 144, 25, 40, 212, 1, 58, 11, 152, 196, 147, 10, 74, 26, 152, 225, 130, 146, 58, 60, 210, 145, 16, 148, 16, 185, 192, 18, 44, 42, 57, 199, 162, 1, 9, 87, 47, 186, 215, 231, 197, 179, 180, 195, 212, 164, 32, 59, 92, 126, 62, - 41, 59, 76, 59, 60, 168, 179, 213, 197, 163, 72, 44, 25, 74, 126, 127, 127, 79, 26, 177, 148, 90, 27, 225, 247, 165, 0, 152, 147, 123, 138, 211, 164, 72, 126, 127, 46, 210, 196, 163, 228, 215, 64, 11, 210, 180, 1, 8, 58, 153, 1, 224, 149, 57, 76, 27, 24, 76, 42, 43, 136, 128, 243, 179, 130, 106, 60, 42, 42, 92, 28, 243, 231, - 147, 24, 57, 44, 58, 94, 45, 8, 57, 139, 214, 148, 40, 77, 26, 9, 16, 10, 144, 64, 62, 43, 25, 123, 59, 138, 162, 48, 63, 26, 41, 92, 60, 43, 176, 3, 59, 232, 214, 164, 16, 75, 75, 76, 60, 153, 179, 33, 62, 26, 136, 40, 75, 169, 197, 163, 129, 57, 60, 59, 75, 138, 145, 64, 63, 138, 179, 1, 42, 136, 90, 43, 176, 214, 180, 1, 25, - 152, 195, 129, 129, 106, 76, 60, 137, 145, 178, 2, 25, 10, 228, 130, 57, 59, 44, 41, 154, 165, 105, 76, 44, 144, 16, 76, 26, 41, 76, 26, 152, 1, 58, 26, 9, 193, 165, 16, 92, 26, 41, 77, 59, 76, 76, 60, 26, 136, 161, 130, 152, 195, 163, 211, 146, 0, 57, 11, 211, 130, 8, 25, 40, 62, 153, 162, 17, 109, 60, 153, 146, 40, 76, 60, - 26, 160, 179, 211, 163, 32, 60, 42, 153, 179, 194, 199, 130, 24, 58, 43, 58, 27, 128, 161, 195, 129, 226, 196, 147, 90, 59, 75, 44, 136, 128, 145, 160, 148, 123, 59, 42, 26, 41, 26, 57, 27, 192, 215, 147, 57, 59, 27, 161, 145, 213, 130, 106, 76, 43, 9, 144, 162, 129, 177, 181, 130, 136, 194, 146, 40, 10, 129, 25, 210, 146, - 178, 197, 196, 179, 196, 130, 8, 41, 9, 144, 178, 130, 209, 182, 17, 92, 43, 176, 147, 144, 212, 130, 136, 0, 177, 130, 73, 62, 10, 161, 130, 91, 75, 59, 43, 57, 46, 25, 41, 77, 10, 177, 164, 16, 26, 136, 210, 197, 179, 130, 128, 57, 77, 43, 25, 75, 10, 227, 179, 180, 179, 146, 128, 57, 185, 183, 163, 145, 0, 8, 8, 10, 119, - 114, 120, 16, 210, 244, 60, 28, 41, 25, 152, 149, 56, 161, 35, 44, 89, 27, 24, 136, 24, 164, 211, 17, 233, 176, 136, 192, 129, 179, 17, 17, 25, 0, 10, 46, 160, 132, 49, 66, 24, 132, 177, 147, 193, 56, 72, 26, 29, 232, 168, 176, 12, 137, 41, 139, 147, 9, 1, 41, 15, 91, 136, 35, 148, 21, 18, 48, 40, 1, 168, 167, 144, 0, 42, 172, - 177, 204, 193, 155, 232, 152, 152, 26, 152, 41, 146, 17, 6, 4, 65, 34, 35, 135, 4, 16, 32, 9, 24, 186, 176, 0, 250, 153, 204, 186, 173, 154, 153, 177, 3, 65, 41, 34, 145, 134, 35, 65, 98, 49, 50, 50, 2, 33, 169, 138, 155, 175, 170, 172, 204, 192, 138, 234, 136, 155, 136, 10, 32, 18, 5, 52, 48, 24, 162, 17, 67, 54, 66, 51, 34, - 131, 184, 174, 234, 153, 10, 9, 40, 0, 152, 251, 168, 142, 154, 9, 16, 33, 49, 33, 128, 154, 170, 156, 34, 54, 54, 33, 68, 0, 1, 136, 201, 137, 26, 88, 48, 35, 99, 8, 152, 189, 189, 187, 155, 171, 16, 24, 130, 145, 188, 175, 203, 144, 49, 115, 67, 67, 50, 19, 2, 1, 0, 0, 130, 131, 1, 136, 206, 216, 188, 203, 204, 187, 187, - 156, 153, 0, 0, 51, 17, 34, 24, 112, 20, 69, 67, 67, 34, 19, 0, 136, 169, 185, 137, 186, 232, 185, 219, 201, 203, 187, 173, 170, 154, 153, 129, 131, 6, 2, 19, 49, 49, 21, 65, 19, 53, 51, 83, 34, 16, 168, 201, 154, 172, 156, 138, 0, 1, 24, 201, 233, 186, 204, 186, 171, 137, 3, 37, 48, 24, 128, 201, 202, 202, 129, 17, 48, 21, - 22, 20, 19, 19, 32, 16, 2, 66, 52, 68, 4, 3, 1, 203, 235, 188, 189, 186, 171, 153, 137, 153, 170, 219, 170, 140, 9, 17, 53, 115, 50, 52, 67, 51, 51, 51, 17, 130, 0, 145, 154, 169, 188, 236, 187, 190, 203, 187, 172, 171, 138, 136, 17, 33, 18, 2, 34, 98, 98, 50, 50, 52, 66, 34, 35, 2, 19, 24, 169, 203, 203, 188, 219, 169, 154, - 9, 137, 171, 204, 188, 203, 184, 136, 34, 83, 50, 33, 153, 184, 170, 170, 152, 40, 57, 19, 36, 50, 50, 18, 35, 17, 2, 49, 49, 66, 66, 66, 34, 17, 168, 233, 202, 202, 170, 171, 170, 186, 219, 203, 188, 188, 154, 138, 25, 33, 68, 52, 68, 67, 67, 36, 51, 36, 18, 17, 17, 136, 8, 170, 176, 202, 188, 206, 202, 171, 172, 186, 169, - 153, 8, 25, 144, 128, 1, 34, 68, 52, 68, 51, 52, 34, 49, 18, 34, 2, 144, 136, 155, 140, 187, 186, 186, 154, 154, 185, 185, 153, 9, 9, 0, 24, 0, 128, 144, 168, 169, 170, 154, 154, 153, 9, 8, 16, 8, 0, 144, 19, 35, 68, 51, 52, 67, 51, 66, 34, 50, 33, 1, 144, 185, 186, 172, 204, 187, 188, 173, 172, 186, 172, 186, 154, 138, 41, - 33, 52, 53, 83, 50, 51, 52, 52, 37, 34, 34, 18, 16, 144, 152, 154, 187, 219, 203, 188, 173, 186, 186, 186, 170, 154, 153, 138, 144, 16, 17, 67, 82, 50, 51, 21, 34, 19, 33, 2, 18, 33, 1, 8, 153, 169, 153, 153, 136, 128, 0, 136, 154, 153, 153, 8, 8, 1, 16, 0, 169, 170, 187, 171, 171, 154, 153, 153, 152, 153, 153, 0, 16, 51, 83, - 66, 50, 67, 50, 51, 67, 51, 52, 35, 18, 136, 186, 219, 187, 189, 186, 171, 187, 173, 187, 188, 187, 203, 138, 9, 16, 33, 50, 52, 53, 67, 67, 147, 8, 128, 128, 128, 128, 128, 128, 128, 128, 0, 240, 255, 55, 232, 23, 220, 0, 148, 1, 9, 18, 148, 10, 189, 32, 163, 62, 160, 5, 137, 12, 149, 42, 153, 144, 34, 42, 8, 1, 138, 181, - 45, 136, 18, 144, 105, 138, 1, 160, 14, 128, 132, 145, 186, 37, 138, 41, 192, 48, 145, 46, 160, 33, 44, 24, 225, 16, 13, 132, 136, 137, 16, 148, 25, 170, 194, 82, 152, 136, 91, 24, 42, 169, 33, 233, 131, 179, 24, 185, 149, 16, 57, 172, 164, 18, 10, 211, 160, 147, 211, 33, 138, 243, 129, 16, 41, 193, 0, 43, 132, 155, 73, - 58, 145, 244, 145, 43, 35, 9, 171, 16, 110, 25, 8, 28, 74, 162, 128, 26, 27, 82, 45, 136, 153, 18, 8, 136, 8 -}; -#endif // DISABLE_PC98_RHYTHM_CHANNEL - -TownsPC98_FmSynth::ChanInternal::ChanInternal() { - memset(this, 0, sizeof(ChanInternal)); -} - -TownsPC98_FmSynth::ChanInternal::~ChanInternal() { - for (uint i = 0; i < ARRAYSIZE(opr); ++i) - delete opr[i]; -} diff --git a/sound/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h b/sound/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h deleted file mode 100644 index ddd249b1b8..0000000000 --- a/sound/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h +++ /dev/null @@ -1,196 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef TOWNS_PC98_FMSYNTH_H -#define TOWNS_PC98_FMSYNTH_H - -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "common/list.h" - -#ifdef __DS__ -/* This disables the rhythm channel when emulating the PC-98 type 86 sound card. - * The only purpose is code size reduction for certain backends. - * At the moment the only games which make use of the rhythm channel are the - * (very rare) PC-98 versions of Legend of Kyrandia 2 and Lands of Lore. Music will - * still be okay, just missing a couple of rhythm instruments. - */ -#define DISABLE_PC98_RHYTHM_CHANNEL -#endif - -class TownsPC98_FmSynthOperator; -class TownsPC98_FmSynthSquareSineSource; -#ifndef DISABLE_PC98_RHYTHM_CHANNEL -class TownsPC98_FmSynthPercussionSource; -#endif - -enum EnvelopeState { - kEnvReady, - kEnvAttacking, - kEnvDecaying, - kEnvSustaining, - kEnvReleasing -}; - -class TownsPC98_FmSynth : public Audio::AudioStream { -public: - enum EmuType { - kTypeTowns, - kType26, - kType86 - }; - - TownsPC98_FmSynth(Audio::Mixer *mixer, EmuType type); - virtual ~TownsPC98_FmSynth(); - - virtual bool init(); - virtual void reset(); - - void writeReg(uint8 part, uint8 regAddress, uint8 value); - - // AudioStream interface - int readBuffer(int16 *buffer, const int numSamples); - bool isStereo() const { - return true; - } - bool endOfData() const { - return false; - } - int getRate() const { - return _mixer->getOutputRate(); - } - -protected: - void deinit(); - - // Implement this in your inherited class if your driver generates - // additional output that has to be inserted into the buffer. - virtual void nextTickEx(int32 *buffer, uint32 bufferSize) {} - - void toggleRegProtection(bool prot) { - _regProtectionFlag = prot; - } - uint8 readSSGStatus(); - - virtual void timerCallbackA() = 0; - virtual void timerCallbackB() = 0; - - // The audio driver can store and apply two different audio settings - // (usually for music and sound effects). The channel mask will determine - // which channels get effected by the setting. The first bits will be - // the normal fm channels, the next bits the ssg channels and the final - // bit the rhythm channel. - void setVolumeIntern(int volA, int volB); - void setVolumeChannelMasks(int channelMaskA, int channelMaskB); - - const int _numChan; - const int _numSSG; - const bool _hasPercussion; - - Common::Mutex _mutex; -private: - void generateTables(); - void nextTick(int32 *buffer, uint32 bufferSize); - void generateOutput(int32 &leftSample, int32 &rightSample, int32 *del, int32 *feed); - - struct ChanInternal { - ChanInternal(); - ~ChanInternal(); - - void ampModSensitivity(uint32 value) { - ampModSvty = (1 << (3 - value)) - (((value >> 1) & 1) | (value & 1)); - } - void frqModSensitivity(uint32 value) { - frqModSvty = value << 5; - } - - uint16 frqTemp; - bool enableLeft; - bool enableRight; - bool updateEnvelopeParameters; - int32 feedbuf[3]; - uint8 algorithm; - - uint32 ampModSvty; - uint32 frqModSvty; - - TownsPC98_FmSynthOperator *opr[4]; - }; - - TownsPC98_FmSynthSquareSineSource *_ssg; -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - TownsPC98_FmSynthPercussionSource *_prc; -#endif - ChanInternal *_chanInternal; - - uint8 *_oprRates; - uint8 *_oprRateshift; - uint8 *_oprAttackDecay; - uint32 *_oprFrq; - uint32 *_oprSinTbl; - int32 *_oprLevelOut; - int32 *_oprDetune; - - bool _regProtectionFlag; - - typedef void (TownsPC98_FmSynth::*ChipTimerProc)(); - void idleTimerCallback() {} - - struct ChipTimer { - bool enabled; - uint16 value; - - int32 smpTillCb; - uint32 smpTillCbRem; - int32 smpPerCb; - uint32 smpPerCbRem; - - ChipTimerProc cb; - }; - - ChipTimer _timers[2]; - - int _volMaskA, _volMaskB; - uint16 _volumeA, _volumeB; - - const float _baserate; - uint32 _timerbase; - uint32 _rtt; - - Audio::Mixer *_mixer; - Audio::SoundHandle _soundHandle; - -#ifndef DISABLE_PC98_RHYTHM_CHANNEL - static const uint8 _percussionData[]; -#endif - static const uint32 _adtStat[]; - static const uint8 _detSrc[]; - static const int _ssgTables[]; - - bool _ready; -}; - -#endif - diff --git a/sound/softsynth/mt32.cpp b/sound/softsynth/mt32.cpp deleted file mode 100644 index 09e7e48151..0000000000 --- a/sound/softsynth/mt32.cpp +++ /dev/null @@ -1,573 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#include "common/scummsys.h" - -#ifdef USE_MT32EMU - -#include "sound/softsynth/mt32/mt32emu.h" - -#include "sound/softsynth/emumidi.h" -#include "sound/musicplugin.h" -#include "sound/mpu401.h" - -#include "common/config-manager.h" -#include "common/debug.h" -#include "common/events.h" -#include "common/file.h" -#include "common/system.h" -#include "common/util.h" -#include "common/archive.h" -#include "common/translation.h" - -#include "graphics/fontman.h" -#include "graphics/surface.h" - -class MidiChannel_MT32 : public MidiChannel_MPU401 { - void effectLevel(byte value) { } - void chorusLevel(byte value) { } -}; - -class MidiDriver_MT32 : public MidiDriver_Emulated { -private: - Audio::SoundHandle _handle; - MidiChannel_MT32 _midiChannels[16]; - uint16 _channelMask; - MT32Emu::Synth *_synth; - - int _outputRate; - -protected: - void generateSamples(int16 *buf, int len); - -public: - bool _initialising; - - MidiDriver_MT32(Audio::Mixer *mixer); - virtual ~MidiDriver_MT32(); - - int open(); - void close(); - void send(uint32 b); - void setPitchBendRange (byte channel, uint range); - void sysEx(const byte *msg, uint16 length); - - uint32 property(int prop, uint32 param); - MidiChannel *allocateChannel(); - MidiChannel *getPercussionChannel(); - - // AudioStream API - bool isStereo() const { return true; } - int getRate() const { return _outputRate; } -}; - -class MT32File : public MT32Emu::File { - Common::File _in; - Common::DumpFile _out; -public: - bool open(const char *filename, OpenMode mode) { - if (mode == OpenMode_read) - return _in.open(filename); - else - return _out.open(filename); - } - void close() { - _in.close(); - _out.close(); - } - size_t read(void *in, size_t size) { - return _in.read(in, size); - } - bool readBit8u(MT32Emu::Bit8u *in) { - byte b = _in.readByte(); - if (_in.eos()) - return false; - *in = b; - return true; - } - size_t write(const void *in, size_t size) { - return _out.write(in, size); - } - bool writeBit8u(MT32Emu::Bit8u out) { - _out.writeByte(out); - return !_out.err(); - } - bool isEOF() { - return _in.isOpen() && _in.eos(); - } -}; - -static int eatSystemEvents() { - Common::Event event; - Common::EventManager *eventMan = g_system->getEventManager(); - while (eventMan->pollEvent(event)) { - switch (event.type) { - case Common::EVENT_QUIT: - return 1; - default: - break; - } - } - return 0; -} - -static void drawProgress(float progress) { - const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kOSDFont)); - Graphics::Surface *screen = g_system->lockScreen(); - - assert(screen); - assert(screen->pixels); - - Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); - - int16 w = g_system->getWidth() / 7 * 5; - int16 h = font.getFontHeight(); - int16 x = g_system->getWidth() / 7; - int16 y = g_system->getHeight() / 2 - h / 2; - - Common::Rect r(x, y, x + w, y + h); - - uint32 col; - - if (screenFormat.bytesPerPixel > 1) - col = screenFormat.RGBToColor(0, 171, 0); - else - col = 1; - - screen->frameRect(r, col); - - r.grow(-1); - r.setWidth(uint16(progress * w)); - - if (screenFormat.bytesPerPixel > 1) - col = screenFormat.RGBToColor(171, 0, 0); - else - col = 2; - - screen->fillRect(r, col); - - g_system->unlockScreen(); - g_system->updateScreen(); -} - -static void drawMessage(int offset, const Common::String &text) { - const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kOSDFont)); - Graphics::Surface *screen = g_system->lockScreen(); - - assert(screen); - assert(screen->pixels); - - Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); - - uint16 h = font.getFontHeight(); - uint16 y = g_system->getHeight() / 2 - h / 2 + offset * (h + 1); - - uint32 col; - - if (screenFormat.bytesPerPixel > 1) - col = screenFormat.RGBToColor(0, 0, 0); - else - col = 0; - - Common::Rect r(0, y, screen->w, y + h); - screen->fillRect(r, col); - - if (screenFormat.bytesPerPixel > 1) - col = screenFormat.RGBToColor(0, 171, 0); - else - col = 1; - - font.drawString(screen, text, 0, y, screen->w, col, Graphics::kTextAlignCenter); - - g_system->unlockScreen(); - g_system->updateScreen(); -} - -static MT32Emu::File *MT32_OpenFile(void *userData, const char *filename, MT32Emu::File::OpenMode mode) { - MT32File *file = new MT32File(); - if (!file->open(filename, mode)) { - delete file; - return NULL; - } - return file; -} - -static void MT32_PrintDebug(void *userData, const char *fmt, va_list list) { - if (((MidiDriver_MT32 *)userData)->_initialising) { - char buf[512]; - - vsnprintf(buf, 512, fmt, list); - buf[70] = 0; // Truncate to a reasonable length - - drawMessage(1, buf); - } - - //vdebug(0, fmt, list); // FIXME: Use a higher debug level -} - -static int MT32_Report(void *userData, MT32Emu::ReportType type, const void *reportData) { - switch (type) { - case MT32Emu::ReportType_lcdMessage: - g_system->displayMessageOnOSD((const char *)reportData); - break; - case MT32Emu::ReportType_errorControlROM: - error("Failed to load MT32_CONTROL.ROM"); - break; - case MT32Emu::ReportType_errorPCMROM: - error("Failed to load MT32_PCM.ROM"); - break; - case MT32Emu::ReportType_progressInit: - if (((MidiDriver_MT32 *)userData)->_initialising) { - drawProgress(*((const float *)reportData)); - return eatSystemEvents(); - } - break; - case MT32Emu::ReportType_availableSSE: - debug(1, "MT32emu: SSE is available"); - break; - case MT32Emu::ReportType_usingSSE: - debug(1, "MT32emu: using SSE"); - break; - case MT32Emu::ReportType_available3DNow: - debug(1, "MT32emu: 3DNow! is available"); - break; - case MT32Emu::ReportType_using3DNow: - debug(1, "MT32emu: using 3DNow!"); - break; - default: - break; - } - return 0; -} - -//////////////////////////////////////// -// -// MidiDriver_MT32 -// -//////////////////////////////////////// - -MidiDriver_MT32::MidiDriver_MT32(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer) { - _channelMask = 0xFFFF; // Permit all 16 channels by default - uint i; - for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) { - _midiChannels[i].init(this, i); - } - _synth = NULL; - // A higher baseFreq reduces the length used in generateSamples(), - // and means that the timer callback will be called more often. - // That results in more accurate timing. - _baseFreq = 10000; - // Unfortunately bugs in the emulator cause inaccurate tuning - // at rates other than 32KHz, thus we produce data at 32KHz and - // rely on Mixer to convert. - _outputRate = 32000; //_mixer->getOutputRate(); - _initialising = false; -} - -MidiDriver_MT32::~MidiDriver_MT32() { - delete _synth; -} - -int MidiDriver_MT32::open() { - MT32Emu::SynthProperties prop; - - if (_isOpen) - return MERR_ALREADY_OPEN; - - MidiDriver_Emulated::open(); - - memset(&prop, 0, sizeof(prop)); - prop.sampleRate = getRate(); - prop.useReverb = true; - prop.useDefaultReverb = false; - prop.reverbType = 0; - prop.reverbTime = 5; - prop.reverbLevel = 3; - prop.userData = this; - prop.printDebug = MT32_PrintDebug; - prop.report = MT32_Report; - prop.openFile = MT32_OpenFile; - - _synth = new MT32Emu::Synth(); - - Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); - - if (screenFormat.bytesPerPixel == 1) { - const byte dummy_palette[] = { - 0, 0, 0, 0, // background - 0, 171, 0, 0, // border, font - 171, 0, 0, 0 // fill - }; - - g_system->getPaletteManager()->setPalette(dummy_palette, 0, 3); - } - - _initialising = true; - drawMessage(-1, _s("Initialising MT-32 Emulator")); - if (!_synth->open(prop)) - return MERR_DEVICE_NOT_AVAILABLE; - _initialising = false; - - if (screenFormat.bytesPerPixel > 1) - g_system->fillScreen(screenFormat.RGBToColor(0, 0, 0)); - else - g_system->fillScreen(0); - - g_system->updateScreen(); - - _mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); - - return 0; -} - -void MidiDriver_MT32::send(uint32 b) { - _synth->playMsg(b); -} - -void MidiDriver_MT32::setPitchBendRange(byte channel, uint range) { - if (range > 24) { - printf("setPitchBendRange() called with range > 24: %d", range); - } - byte benderRangeSysex[9]; - benderRangeSysex[0] = 0x41; // Roland - benderRangeSysex[1] = channel; - benderRangeSysex[2] = 0x16; // MT-32 - benderRangeSysex[3] = 0x12; // Write - benderRangeSysex[4] = 0x00; - benderRangeSysex[5] = 0x00; - benderRangeSysex[6] = 0x04; - benderRangeSysex[7] = (byte)range; - benderRangeSysex[8] = MT32Emu::Synth::calcSysexChecksum(&benderRangeSysex[4], 4, 0); - sysEx(benderRangeSysex, 9); -} - -void MidiDriver_MT32::sysEx(const byte *msg, uint16 length) { - if (msg[0] == 0xf0) { - _synth->playSysex(msg, length); - } else { - _synth->playSysexWithoutFraming(msg, length); - } -} - -void MidiDriver_MT32::close() { - if (!_isOpen) - return; - _isOpen = false; - - // Detach the player callback handler - setTimerCallback(NULL, NULL); - // Detach the mixer callback handler - _mixer->stopHandle(_handle); - - _synth->close(); - delete _synth; - _synth = NULL; -} - -void MidiDriver_MT32::generateSamples(int16 *data, int len) { - _synth->render(data, len); -} - -uint32 MidiDriver_MT32::property(int prop, uint32 param) { - switch (prop) { - case PROP_CHANNEL_MASK: - _channelMask = param & 0xFFFF; - return 1; - } - - return 0; -} - -MidiChannel *MidiDriver_MT32::allocateChannel() { - MidiChannel_MT32 *chan; - uint i; - - for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) { - if (i == 9 || !(_channelMask & (1 << i))) - continue; - chan = &_midiChannels[i]; - if (chan->allocate()) { - return chan; - } - } - return NULL; -} - -MidiChannel *MidiDriver_MT32::getPercussionChannel() { - return &_midiChannels[9]; -} - -// This code should be used when calling the timer callback from the mixer thread is undesirable. -// Note that it results in less accurate timing. -#if 0 -class MidiEvent_MT32 { -public: - MidiEvent_MT32 *_next; - uint32 _msg; // 0xFFFFFFFF indicates a sysex message - byte *_data; - uint32 _len; - - MidiEvent_MT32(uint32 msg, byte *data, uint32 len) { - _msg = msg; - if (len > 0) { - _data = new byte[len]; - memcpy(_data, data, len); - } - _len = len; - _next = NULL; - } - - MidiEvent_MT32() { - if (_len > 0) - delete _data; - } -}; - -class MidiDriver_ThreadedMT32 : public MidiDriver_MT32 { -private: - OSystem::Mutex _eventMutex; - MidiEvent_MT32 *_events; - TimerManager::TimerProc _timer_proc; - - void pushMidiEvent(MidiEvent_MT32 *event); - MidiEvent_MT32 *popMidiEvent(); - -protected: - void send(uint32 b); - void sysEx(const byte *msg, uint16 length); - -public: - MidiDriver_ThreadedMT32(Audio::Mixer *mixer); - - void onTimer(); - void close(); - void setTimerCallback(void *timer_param, TimerManager::TimerProc timer_proc); -}; - - -MidiDriver_ThreadedMT32::MidiDriver_ThreadedMT32(Audio::Mixer *mixer) : MidiDriver_MT32(mixer) { - _events = NULL; - _timer_proc = NULL; -} - -void MidiDriver_ThreadedMT32::close() { - MidiDriver_MT32::close(); - while ((popMidiEvent() != NULL)) { - // Just eat any leftover events - } -} - -void MidiDriver_ThreadedMT32::setTimerCallback(void *timer_param, TimerManager::TimerProc timer_proc) { - if (!_timer_proc || !timer_proc) { - if (_timer_proc) - _vm->_timer->removeTimerProc(_timer_proc); - _timer_proc = timer_proc; - if (timer_proc) - _vm->_timer->installTimerProc(timer_proc, getBaseTempo(), timer_param); - } -} - -void MidiDriver_ThreadedMT32::pushMidiEvent(MidiEvent_MT32 *event) { - Common::StackLock lock(_eventMutex); - if (_events == NULL) { - _events = event; - } else { - MidiEvent_MT32 *last = _events; - while (last->_next != NULL) - last = last->_next; - last->_next = event; - } -} - -MidiEvent_MT32 *MidiDriver_ThreadedMT32::popMidiEvent() { - Common::StackLock lock(_eventMutex); - MidiEvent_MT32 *event; - event = _events; - if (event != NULL) - _events = event->_next; - return event; -} - -void MidiDriver_ThreadedMT32::send(uint32 b) { - MidiEvent_MT32 *event = new MidiEvent_MT32(b, NULL, 0); - pushMidiEvent(event); -} - -void MidiDriver_ThreadedMT32::sysEx(const byte *msg, uint16 length) { - MidiEvent_MT32 *event = new MidiEvent_MT32(0xFFFFFFFF, msg, length); - pushMidiEvent(event); -} - -void MidiDriver_ThreadedMT32::onTimer() { - MidiEvent_MT32 *event; - while ((event = popMidiEvent()) != NULL) { - if (event->_msg == 0xFFFFFFFF) { - MidiDriver_MT32::sysEx(event->_data, event->_len); - } else { - MidiDriver_MT32::send(event->_msg); - } - delete event; - } -} -#endif - - -// Plugin interface - -class MT32EmuMusicPlugin : public MusicPluginObject { -public: - const char *getName() const { - return _s("MT-32 Emulator"); - } - - const char *getId() const { - return "mt32"; - } - - MusicDevices getDevices() const; - Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const; -}; - -MusicDevices MT32EmuMusicPlugin::getDevices() const { - MusicDevices devices; - devices.push_back(MusicDevice(this, "", MT_MT32)); - return devices; -} - -Common::Error MT32EmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const { - if (ConfMan.hasKey("extrapath")) - SearchMan.addDirectory("extrapath", ConfMan.get("extrapath")); - - *mididriver = new MidiDriver_MT32(g_system->getMixer()); - - return Common::kNoError; -} - -//#if PLUGIN_ENABLED_DYNAMIC(MT32) - //REGISTER_PLUGIN_DYNAMIC(MT32, PLUGIN_TYPE_MUSIC, MT32EmuMusicPlugin); -//#else - REGISTER_PLUGIN_STATIC(MT32, PLUGIN_TYPE_MUSIC, MT32EmuMusicPlugin); -//#endif - -#endif diff --git a/sound/softsynth/mt32/freeverb.cpp b/sound/softsynth/mt32/freeverb.cpp deleted file mode 100644 index 4ee6e20117..0000000000 --- a/sound/softsynth/mt32/freeverb.cpp +++ /dev/null @@ -1,310 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -// Comb filter implementation -// -// Written by -// http://www.dreampoint.co.uk -// This code is public domain - -#include "sound/softsynth/mt32/freeverb.h" - -comb::comb() { - filterstore = 0; - bufidx = 0; -} - -void comb::setbuffer(float *buf, int size) { - buffer = buf; - bufsize = size; -} - -void comb::mute() { - for (int i = 0; i < bufsize; i++) - buffer[i] = 0; -} - -void comb::setdamp(float val) { - damp1 = val; - damp2 = 1 - val; -} - -float comb::getdamp() { - return damp1; -} - -void comb::setfeedback(float val) { - feedback = val; -} - -float comb::getfeedback() { - return feedback; -} - -// Allpass filter implementation - -allpass::allpass() { - bufidx = 0; -} - -void allpass::setbuffer(float *buf, int size) { - buffer = buf; - bufsize = size; -} - -void allpass::mute() { - for (int i = 0; i < bufsize; i++) - buffer[i] = 0; -} - -void allpass::setfeedback(float val) { - feedback = val; -} - -float allpass::getfeedback() { - return feedback; -} - -// Reverb model implementation - -revmodel::revmodel() { - // Tie the components to their buffers - combL[0].setbuffer(bufcombL1,combtuningL1); - combR[0].setbuffer(bufcombR1,combtuningR1); - combL[1].setbuffer(bufcombL2,combtuningL2); - combR[1].setbuffer(bufcombR2,combtuningR2); - combL[2].setbuffer(bufcombL3,combtuningL3); - combR[2].setbuffer(bufcombR3,combtuningR3); - combL[3].setbuffer(bufcombL4,combtuningL4); - combR[3].setbuffer(bufcombR4,combtuningR4); - combL[4].setbuffer(bufcombL5,combtuningL5); - combR[4].setbuffer(bufcombR5,combtuningR5); - combL[5].setbuffer(bufcombL6,combtuningL6); - combR[5].setbuffer(bufcombR6,combtuningR6); - combL[6].setbuffer(bufcombL7,combtuningL7); - combR[6].setbuffer(bufcombR7,combtuningR7); - combL[7].setbuffer(bufcombL8,combtuningL8); - combR[7].setbuffer(bufcombR8,combtuningR8); - allpassL[0].setbuffer(bufallpassL1,allpasstuningL1); - allpassR[0].setbuffer(bufallpassR1,allpasstuningR1); - allpassL[1].setbuffer(bufallpassL2,allpasstuningL2); - allpassR[1].setbuffer(bufallpassR2,allpasstuningR2); - allpassL[2].setbuffer(bufallpassL3,allpasstuningL3); - allpassR[2].setbuffer(bufallpassR3,allpasstuningR3); - allpassL[3].setbuffer(bufallpassL4,allpasstuningL4); - allpassR[3].setbuffer(bufallpassR4,allpasstuningR4); - - // Set default values - allpassL[0].setfeedback(0.5f); - allpassR[0].setfeedback(0.5f); - allpassL[1].setfeedback(0.5f); - allpassR[1].setfeedback(0.5f); - allpassL[2].setfeedback(0.5f); - allpassR[2].setfeedback(0.5f); - allpassL[3].setfeedback(0.5f); - allpassR[3].setfeedback(0.5f); - setmode(initialmode); - setwet(initialwet); - setroomsize(initialroom); - setdry(initialdry); - setdamp(initialdamp); - setwidth(initialwidth); - - // Buffer will be full of rubbish - so we MUST mute them - mute(); -} - -void revmodel::mute() { - int i; - - if (getmode() >= freezemode) - return; - - for (i = 0; i < numcombs; i++) { - combL[i].mute(); - combR[i].mute(); - } - - for (i = 0; i < numallpasses; i++) { - allpassL[i].mute(); - allpassR[i].mute(); - } -} - -void revmodel::processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) { - float outL, outR, input; - - while (numsamples-- > 0) { - int i; - - outL = outR = 0; - input = (*inputL + *inputR) * gain; - - // Accumulate comb filters in parallel - for (i = 0; i < numcombs; i++) { - outL += combL[i].process(input); - outR += combR[i].process(input); - } - - // Feed through allpasses in series - for (i = 0; i < numallpasses; i++) { - outL = allpassL[i].process(outL); - outR = allpassR[i].process(outR); - } - - // Calculate output REPLACING anything already there - *outputL = outL * wet1 + outR * wet2 + *inputL * dry; - *outputR = outR * wet1 + outL * wet2 + *inputR * dry; - - // Increment sample pointers, allowing for interleave (if any) - inputL += skip; - inputR += skip; - outputL += skip; - outputR += skip; - } -} - -void revmodel::processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) { - float outL, outR, input; - - while (numsamples-- > 0) { - int i; - - outL = outR = 0; - input = (*inputL + *inputR) * gain; - - // Accumulate comb filters in parallel - for (i = 0; i < numcombs; i++) { - outL += combL[i].process(input); - outR += combR[i].process(input); - } - - // Feed through allpasses in series - for (i = 0; i < numallpasses; i++) { - outL = allpassL[i].process(outL); - outR = allpassR[i].process(outR); - } - - // Calculate output MIXING with anything already there - *outputL += outL * wet1 + outR * wet2 + *inputL * dry; - *outputR += outR * wet1 + outL * wet2 + *inputR * dry; - - // Increment sample pointers, allowing for interleave (if any) - inputL += skip; - inputR += skip; - outputL += skip; - outputR += skip; - } -} - -void revmodel::update() { - // Recalculate internal values after parameter change - - int i; - - wet1 = wet * (width / 2 + 0.5f); - wet2 = wet * ((1 - width) / 2); - - if (mode >= freezemode) { - roomsize1 = 1; - damp1 = 0; - gain = muted; - } else { - roomsize1 = roomsize; - damp1 = damp; - gain = fixedgain; - } - - for (i = 0; i < numcombs; i++) { - combL[i].setfeedback(roomsize1); - combR[i].setfeedback(roomsize1); - } - - for (i = 0; i < numcombs; i++) { - combL[i].setdamp(damp1); - combR[i].setdamp(damp1); - } -} - -// The following get/set functions are not inlined, because -// speed is never an issue when calling them, and also -// because as you develop the reverb model, you may -// wish to take dynamic action when they are called. - -void revmodel::setroomsize(float value) { - roomsize = (value * scaleroom) + offsetroom; - update(); -} - -float revmodel::getroomsize() { - return (roomsize - offsetroom) / scaleroom; -} - -void revmodel::setdamp(float value) { - damp = value * scaledamp; - update(); -} - -float revmodel::getdamp() { - return damp / scaledamp; -} - -void revmodel::setwet(float value) { - wet = value * scalewet; - update(); -} - -float revmodel::getwet() { - return wet / scalewet; -} - -void revmodel::setdry(float value) { - dry = value * scaledry; -} - -float revmodel::getdry() { - return dry / scaledry; -} - -void revmodel::setwidth(float value) { - width = value; - update(); -} - -float revmodel::getwidth() { - return width; -} - -void revmodel::setmode(float value) { - mode = value; - update(); -} - -float revmodel::getmode() { - if (mode >= freezemode) - return 1; - else - return 0; -} diff --git a/sound/softsynth/mt32/freeverb.h b/sound/softsynth/mt32/freeverb.h deleted file mode 100644 index 8310aca3e3..0000000000 --- a/sound/softsynth/mt32/freeverb.h +++ /dev/null @@ -1,244 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -// Macro for killing denormalled numbers -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// Based on IS_DENORMAL macro by Jon Watte -// This code is public domain - -#ifndef FREEVERB_H -#define FREEVERB_H - -// FIXME: Fix this really ugly hack -inline float undenormalise(void *sample) { - if (((*(unsigned int*)sample) & 0x7f800000) == 0) - return 0.0f; - return *(float*)sample; -} - -// Comb filter class declaration - -class comb { -public: - comb(); - void setbuffer(float *buf, int size); - inline float process(float inp); - void mute(); - void setdamp(float val); - float getdamp(); - void setfeedback(float val); - float getfeedback(); -private: - float feedback; - float filterstore; - float damp1; - float damp2; - float *buffer; - int bufsize; - int bufidx; -}; - - -// Big to inline - but crucial for speed - -inline float comb::process(float input) { - float output; - - output = buffer[bufidx]; - undenormalise(&output); - - filterstore = (output * damp2) + (filterstore * damp1); - undenormalise(&filterstore); - - buffer[bufidx] = input + (filterstore * feedback); - - if (++bufidx >= bufsize) - bufidx = 0; - - return output; -} - -// Allpass filter declaration - -class allpass { -public: - allpass(); - void setbuffer(float *buf, int size); - inline float process(float inp); - void mute(); - void setfeedback(float val); - float getfeedback(); -private: - float feedback; - float *buffer; - int bufsize; - int bufidx; -}; - - -// Big to inline - but crucial for speed - -inline float allpass::process(float input) { - float output; - float bufout; - - bufout = buffer[bufidx]; - undenormalise(&bufout); - - output = -input + bufout; - buffer[bufidx] = input + (bufout * feedback); - - if (++bufidx >= bufsize) - bufidx = 0; - - return output; -} - - -// Reverb model tuning values - -const int numcombs = 8; -const int numallpasses = 4; -const float muted = 0; -const float fixedgain = 0.015f; -const float scalewet = 3; -const float scaledry = 2; -const float scaledamp = 0.4f; -const float scaleroom = 0.28f; -const float offsetroom = 0.7f; -const float initialroom = 0.5f; -const float initialdamp = 0.5f; -const float initialwet = 1 / scalewet; -const float initialdry = 0; -const float initialwidth = 1; -const float initialmode = 0; -const float freezemode = 0.5f; -const int stereospread = 23; - -// These values assume 44.1KHz sample rate -// they will probably be OK for 48KHz sample rate -// but would need scaling for 96KHz (or other) sample rates. -// The values were obtained by listening tests. -const int combtuningL1 = 1116; -const int combtuningR1 = 1116 + stereospread; -const int combtuningL2 = 1188; -const int combtuningR2 = 1188 + stereospread; -const int combtuningL3 = 1277; -const int combtuningR3 = 1277 + stereospread; -const int combtuningL4 = 1356; -const int combtuningR4 = 1356 + stereospread; -const int combtuningL5 = 1422; -const int combtuningR5 = 1422 + stereospread; -const int combtuningL6 = 1491; -const int combtuningR6 = 1491 + stereospread; -const int combtuningL7 = 1557; -const int combtuningR7 = 1557 + stereospread; -const int combtuningL8 = 1617; -const int combtuningR8 = 1617 + stereospread; -const int allpasstuningL1 = 556; -const int allpasstuningR1 = 556 + stereospread; -const int allpasstuningL2 = 441; -const int allpasstuningR2 = 441 + stereospread; -const int allpasstuningL3 = 341; -const int allpasstuningR3 = 341 + stereospread; -const int allpasstuningL4 = 225; -const int allpasstuningR4 = 225 + stereospread; - - -// Reverb model declaration - -class revmodel { -public: - revmodel(); - void mute(); - void processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); - void processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); - void setroomsize(float value); - float getroomsize(); - void setdamp(float value); - float getdamp(); - void setwet(float value); - float getwet(); - void setdry(float value); - float getdry(); - void setwidth(float value); - float getwidth(); - void setmode(float value); - float getmode(); -private: - void update(); - - float gain; - float roomsize, roomsize1; - float damp, damp1; - float wet, wet1, wet2; - float dry; - float width; - float mode; - - // The following are all declared inline - // to remove the need for dynamic allocation - // with its subsequent error-checking messiness - - // Comb filters - comb combL[numcombs]; - comb combR[numcombs]; - - // Allpass filters - allpass allpassL[numallpasses]; - allpass allpassR[numallpasses]; - - // Buffers for the combs - float bufcombL1[combtuningL1]; - float bufcombR1[combtuningR1]; - float bufcombL2[combtuningL2]; - float bufcombR2[combtuningR2]; - float bufcombL3[combtuningL3]; - float bufcombR3[combtuningR3]; - float bufcombL4[combtuningL4]; - float bufcombR4[combtuningR4]; - float bufcombL5[combtuningL5]; - float bufcombR5[combtuningR5]; - float bufcombL6[combtuningL6]; - float bufcombR6[combtuningR6]; - float bufcombL7[combtuningL7]; - float bufcombR7[combtuningR7]; - float bufcombL8[combtuningL8]; - float bufcombR8[combtuningR8]; - - // Buffers for the allpasses - float bufallpassL1[allpasstuningL1]; - float bufallpassR1[allpasstuningR1]; - float bufallpassL2[allpasstuningL2]; - float bufallpassR2[allpasstuningR2]; - float bufallpassL3[allpasstuningL3]; - float bufallpassR3[allpasstuningR3]; - float bufallpassL4[allpasstuningL4]; - float bufallpassR4[allpasstuningR4]; -}; - -#endif diff --git a/sound/softsynth/mt32/i386.cpp b/sound/softsynth/mt32/i386.cpp deleted file mode 100644 index f092189d76..0000000000 --- a/sound/softsynth/mt32/i386.cpp +++ /dev/null @@ -1,849 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include "mt32emu.h" - -#ifdef MT32EMU_HAVE_X86 - -namespace MT32Emu { - -#ifndef _MSC_VER - -#define eflag(value) __asm__ __volatile__("pushfl \n popfl \n" : : "a"(value)) -#define cpuid_flag (1 << 21) - -static inline bool atti386_DetectCPUID() { - unsigned int result; - - // Is there a cpuid? - result = cpuid_flag; // set test - eflag(result); - if (!(result & cpuid_flag)) - return false; - - result = 0; // clear test - eflag(result); - if (result & cpuid_flag) - return false; - - return true; -} - -static inline bool atti386_DetectSIMD() { - unsigned int result; - - if (atti386_DetectCPUID() == false) - return false; - - /* check cpuid */ - __asm__ __volatile__( - "pushl %%ebx \n" \ - "movl $1, %%eax \n" \ - "cpuid \n" \ - "movl %%edx, %0 \n" \ - "popl %%ebx \n" \ - : "=r"(result) : : "eax", "ecx", "edx"); - - if (result & (1 << 25)) - return true; - - return false; -} - -static inline bool atti386_Detect3DNow() { - unsigned int result; - - if (atti386_DetectCPUID() == false) - return false; - - // get cpuid - __asm__ __volatile__( - "pushl %%ebx \n" \ - "movl $0x80000001, %%eax \n" \ - "cpuid \n" \ - "movl %%edx, %0 \n" \ - "popl %%ebx \n" \ - : "=r"(result) : : "eax", "ecx", "edx"); - - if (result & 0x80000000) - return true; - - return false; -} - - -static inline float atti386_iir_filter_sse(float *output, float *hist1_ptr, float *coef_ptr) { - __asm__ __volatile__ ( - "pushl %1 \n" \ - "pushl %2 \n" \ - "movss 0(%0), %%xmm1 \n" \ - "movups 0(%1), %%xmm2 \n" \ - "movlps 0(%2), %%xmm3 \n" \ - " \n" \ - "shufps $0x44, %%xmm3, %%xmm3 \n" \ - " \n" \ - "mulps %%xmm3, %%xmm2 \n" \ - " \n" \ - "subss %%xmm2, %%xmm1 \n" \ - "shufps $0x39, %%xmm2, %%xmm2 \n" \ - "subss %%xmm2, %%xmm1 \n" \ - " \n" \ - "movss %%xmm1, 0(%2) \n" \ - " \n" \ - "shufps $0x39, %%xmm2, %%xmm2 \n" \ - "addss %%xmm2, %%xmm1 \n" \ - " \n" \ - "shufps $0x39, %%xmm2, %%xmm2 \n" \ - "addss %%xmm2, %%xmm1 \n" \ - " \n" \ - "movss %%xmm3, 4(%2) \n" \ - " \n" \ - "addl $16, %1 \n" \ - "addl $8, %2 \n" \ - " \n" \ - "movups 0(%1), %%xmm2 \n" \ - " \n" \ - "movlps 0(%2), %%xmm3 \n" \ - "shufps $0x44, %%xmm3, %%xmm3 \n" \ - " \n" \ - "mulps %%xmm3, %%xmm2 \n" \ - " \n" \ - "subss %%xmm2, %%xmm1 \n" \ - "shufps $0x39, %%xmm2, %%xmm2 \n" \ - "subss %%xmm2, %%xmm1 \n" \ - " \n" \ - "movss %%xmm1, 0(%2) \n" \ - " \n" \ - "shufps $0x39, %%xmm2, %%xmm2 \n" \ - "addss %%xmm2, %%xmm1 \n" \ - " \n" \ - "shufps $0x39, %%xmm2, %%xmm2 \n" \ - "addss %%xmm2, %%xmm1 \n" \ - " \n" \ - "movss %%xmm3, 4(%2) \n" \ - "movss %%xmm1, 0(%0) \n" \ - "popl %2 \n" \ - "popl %1 \n" \ - : : "r"(output), "r"(coef_ptr), "r"(hist1_ptr) - : "memory" -#ifdef __SSE__ - , "xmm1", "xmm2", "xmm3" -#endif - ); - - return *output; -} - -static inline float atti386_iir_filter_3DNow(float output, float *hist1_ptr, float *coef_ptr) { - float tmp; - - __asm__ __volatile__ ( - "movq %0, %%mm1 \n" \ - " \n" \ - "movl %1, %%edi \n" \ - "movq 0(%%edi), %%mm2 \n" \ - " \n" \ - "movl %2, %%eax; \n" \ - "movq 0(%%eax), %%mm3 \n" \ - " \n" \ - "pfmul %%mm3, %%mm2 \n" \ - "pfsub %%mm2, %%mm1 \n" \ - " \n" \ - "psrlq $32, %%mm2 \n" \ - "pfsub %%mm2, %%mm1 \n" \ - " \n" \ - "movd %%mm1, %3 \n" \ - " \n" \ - "addl $8, %%edi \n" \ - "movq 0(%%edi), %%mm2 \n" \ - "movq 0(%%eax), %%mm3 \n" \ - " \n" \ - "pfmul %%mm3, %%mm2 \n" \ - "pfadd %%mm2, %%mm1 \n" \ - " \n" \ - "psrlq $32, %%mm2 \n" \ - "pfadd %%mm2, %%mm1 \n" \ - " \n" \ - "pushl %3 \n" \ - "popl 0(%%eax) \n" \ - " \n" \ - "movd %%mm3, 4(%%eax) \n" \ - " \n" \ - "addl $8, %%edi \n" \ - "addl $8, %%eax \n" \ - " \n" \ - "movq 0(%%edi), %%mm2 \n" \ - "movq 0(%%eax), %%mm3 \n" \ - " \n" \ - "pfmul %%mm3, %%mm2 \n" \ - "pfsub %%mm2, %%mm1 \n" \ - " \n" \ - "psrlq $32, %%mm2 \n" \ - "pfsub %%mm2, %%mm1 \n" \ - " \n" \ - "movd %%mm1, %3 \n" \ - " \n" \ - "addl $8, %%edi \n" \ - "movq 0(%%edi), %%mm2 \n" \ - "movq 0(%%eax), %%mm3 \n" \ - " \n" \ - "pfmul %%mm3, %%mm2 \n" \ - "pfadd %%mm2, %%mm1 \n" \ - " \n" \ - "psrlq $32, %%mm2 \n" \ - "pfadd %%mm2, %%mm1 \n" \ - " \n" \ - "pushl %3 \n" \ - "popl 0(%%eax) \n" \ - "movd %%mm3, 4(%%eax) \n" \ - " \n" \ - "movd %%mm1, %0 \n" \ - "femms \n" \ - : "=m"(output) : "g"(coef_ptr), "g"(hist1_ptr), "m"(tmp) - : "eax", "edi", "memory" -#ifdef __MMX__ - , "mm1", "mm2", "mm3" -#endif - ); - - return output; -} - -static inline void atti386_produceOutput1(int tmplen, Bit16s myvolume, Bit16s *useBuf, Bit16s *snd) { - __asm__ __volatile__( - "movl %0, %%ecx \n" \ - "movw %1, %%ax \n" \ - "shll $16, %%eax \n" \ - "movw %1, %%ax \n" \ - "movd %%eax, %%mm3 \n" \ - "movd %%eax, %%mm2 \n" \ - "psllq $32, %%mm3 \n" \ - "por %%mm2, %%mm3 \n" \ - "movl %2, %%esi \n" \ - "movl %3, %%edi \n" \ - "1: \n" \ - "movq 0(%%esi), %%mm1 \n" \ - "movq 0(%%edi), %%mm2 \n" \ - "pmulhw %%mm3, %%mm1 \n" \ - "paddw %%mm2, %%mm1 \n" \ - "movq %%mm1, 0(%%edi) \n" \ - " \n" \ - "addl $8, %%esi \n" \ - "addl $8, %%edi \n" \ - " \n" \ - "decl %%ecx \n" \ - "cmpl $0, %%ecx \n" \ - "jg 1b \n" \ - "emms \n" \ - : : "g"(tmplen), "g"(myvolume), "g"(useBuf), "g"(snd) - : "eax", "ecx", "edi", "esi", "memory" -#ifdef __MMX__ - , "mm1", "mm2", "mm3" -#endif - ); -} - -static inline void atti386_produceOutput2(Bit32u len, Bit16s *snd, float *sndbufl, float *sndbufr, float *multFactor) { - __asm__ __volatile__( - "movl %4, %%ecx \n" \ - "shrl $1, %%ecx \n" \ - "addl $4, %%ecx \n" \ - "pushl %%ecx \n" \ - " \n" \ - "movl %0, %%esi \n" \ - "movups 0(%%esi), %%xmm1 \n" \ - " \n" \ - "movl %1, %%esi \n" \ - "movl %2, %%edi \n" \ - "1: \n" \ - "xorl %%eax, %%eax \n" \ - "movw 0(%1), %%ax \n" \ - "cwde \n" \ - "incl %1 \n" \ - "incl %1 \n" \ - "movd %%eax, %%mm1 \n" \ - "psrlq $32, %%mm1 \n" \ - "movw 0(%1), %%ax \n" \ - "incl %1 \n" \ - "incl %1 \n" \ - "movd %%eax, %%mm2 \n" \ - "por %%mm2, %%mm1 \n" \ - " \n" \ - "decl %%ecx \n" \ - "jnz 1b \n" \ - " \n" \ - "popl %%ecx \n" \ - "movl %1, %%esi \n" \ - "movl %3, %%edi \n" \ - "incl %%esi \n" \ - "2: \n" \ - "decl %%ecx \n" \ - "jnz 2b \n" \ - : : "g"(multFactor), "r"(snd), "g"(sndbufl), "g"(sndbufr), "g"(len) - : "eax", "ecx", "edi", "esi", "mm1", "mm2", "xmm1", "memory"); -} - -static inline void atti386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { - __asm__ __volatile__( - "movl %0, %%ecx \n" \ - "movl %1, %%esi \n" \ - "movl %2, %%edi \n" \ - "1: \n" \ - "movq 0(%%edi), %%mm1 \n" \ - "movq 0(%%esi), %%mm2 \n" \ - "paddw %%mm2, %%mm1 \n" \ - "movq %%mm1, 0(%%esi) \n" \ - "addl $8, %%edi \n" \ - "addl $8, %%esi \n" \ - "decl %%ecx \n" \ - "cmpl $0, %%ecx \n" \ - "jg 1b \n" \ - "emms \n" \ - : : "g"(len), "g"(buf1), "g"(buf2) - : "ecx", "edi", "esi", "memory" -#ifdef __MMX__ - , "mm1", "mm2" -#endif - ); -} - -static inline void atti386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { - __asm__ __volatile__( - "movl %0, %%ecx \n" \ - "movl %1, %%esi \n" \ - "movl %2, %%edi \n" \ - "1: \n" \ - "movq 0(%%esi), %%mm1 \n" \ - "movq 0(%%edi), %%mm2 \n" \ - "movq %%mm1, %%mm3 \n" \ - "pmulhw %%mm2, %%mm1 \n" \ - "paddw %%mm3, %%mm1 \n" \ - "movq %%mm1, 0(%%esi) \n" \ - "addl $8, %%edi \n" \ - "addl $8, %%esi \n" \ - "decl %%ecx \n" \ - "cmpl $0, %%ecx \n" \ - "jg 1b \n" \ - "emms \n" \ - : : "g"(len), "g"(buf1), "g"(buf2) - : "ecx", "edi", "esi", "memory" -#ifdef __MMX__ - , "mm1", "mm2", "mm3" -#endif - ); -} - -static inline void atti386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { - __asm__ __volatile__( - "movl %0, %%ecx \n" \ - "movl %1, %%esi \n" \ - "movl %2, %%edi \n" \ - "1: \n" \ - "movq 0(%%esi), %%mm1 \n" \ - "movq 0(%%edi), %%mm2 \n" \ - "pmulhw %%mm2, %%mm1 \n" \ - "movq %%mm1, 0(%%esi) \n" \ - "addl $8, %%edi \n" \ - "addl $8, %%esi \n" \ - "decl %%ecx \n" \ - "cmpl $0, %%ecx \n" \ - "jg 1b \n" \ - "emms \n" \ - : : "g"(len), "g"(buf1), "g"(buf2) - : "ecx", "edi", "esi", "memory" -#ifdef __MMX__ - , "mm1", "mm2" -#endif - ); -} - -static inline void atti386_partialProductOutput(int quadlen, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *p1buf) { - __asm__ __volatile__( - "movl %0, %%ecx \n" \ - "movw %1, %%ax \n" \ - "shll $16, %%eax \n" \ - "movw %2, %%ax \n" \ - "movd %%eax, %%mm1 \n" \ - "movd %%eax, %%mm2 \n" \ - "psllq $32, %%mm1 \n" \ - "por %%mm2, %%mm1 \n" \ - "movl %3, %%edi \n" \ - "movl %4, %%esi \n" \ - "pushl %%ebx \n" \ - "1: \n" \ - "movw 0(%%esi), %%bx \n" \ - "addl $2, %%esi \n" \ - "movw 0(%%esi), %%dx \n" \ - "addl $2, %%esi \n" \ - "" \ - "movw %%dx, %%ax \n" \ - "shll $16, %%eax \n" \ - "movw %%dx, %%ax \n" \ - "movd %%eax, %%mm2 \n" \ - "psllq $32, %%mm2 \n" \ - "movw %%bx, %%ax \n" \ - "shll $16, %%eax \n" \ - "movw %%bx, %%ax \n" \ - "movd %%eax, %%mm3 \n" \ - "por %%mm3, %%mm2 \n" \ - "" \ - "pmulhw %%mm1, %%mm2 \n" \ - "movq %%mm2, 0(%%edi) \n" \ - "addl $8, %%edi \n" \ - "" \ - "decl %%ecx \n" \ - "cmpl $0, %%ecx \n" \ - "jg 1b \n" \ - "emms \n" \ - "popl %%ebx \n" \ - : : "g"(quadlen), "g"(leftvol), "g"(rightvol), "g"(partialBuf), "g"(p1buf) - : "eax", "ecx", "edx", "edi", "esi", "memory" -#ifdef __MMX__ - , "mm1", "mm2", "mm3" -#endif - ); -} - -#endif - -bool DetectSIMD() { -#ifdef _MSC_VER - bool found_simd; - __asm { - pushfd - pop eax // get EFLAGS into eax - mov ebx,eax // keep a copy - xor eax,0x200000 - // toggle CPUID bit - - push eax - popfd // set new EFLAGS - pushfd - pop eax // EFLAGS back into eax - - xor eax,ebx - // have we changed the ID bit? - - je NO_SIMD - // No, no CPUID instruction - - // we could toggle the - // ID bit so CPUID is present - mov eax,1 - - cpuid // get processor features - test edx,1<<25 // check the SIMD bit - jz NO_SIMD - mov found_simd,1 - jmp DONE - NO_SIMD: - mov found_simd,0 - DONE: - } - return found_simd; -#else - return atti386_DetectSIMD(); -#endif -} - -bool Detect3DNow() { -#ifdef _MSC_VER - bool found3D = false; - __asm { - pushfd - pop eax - mov edx, eax - xor eax, 00200000h - push eax - popfd - pushfd - pop eax - xor eax, edx - jz NO_3DNOW - - mov eax, 80000000h - cpuid - - cmp eax, 80000000h - jbe NO_3DNOW - - mov eax, 80000001h - cpuid - test edx, 80000000h - jz NO_3DNOW - mov found3D, 1 -NO_3DNOW: - - } - return found3D; -#else - return atti386_Detect3DNow(); -#endif -} - -float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr) { - float output; - - // 1st number of coefficients array is overall input scale factor, or filter gain - output = input * (*coef_ptr++); - -#ifdef _MSC_VER - __asm { - - movss xmm1, output - - mov eax, coef_ptr - movups xmm2, [eax] - - mov eax, hist1_ptr - movlps xmm3, [eax] - shufps xmm3, xmm3, 44h - // hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr - - mulps xmm2, xmm3 - - subss xmm1, xmm2 - // Rotate elements right - shufps xmm2, xmm2, 39h - subss xmm1, xmm2 - - // Store new_hist - movss DWORD PTR [eax], xmm1 - - // Rotate elements right - shufps xmm2, xmm2, 39h - addss xmm1, xmm2 - - // Rotate elements right - shufps xmm2, xmm2, 39h - addss xmm1, xmm2 - - // Store previous hist - movss DWORD PTR [eax+4], xmm3 - - add coef_ptr, 16 - add hist1_ptr, 8 - - mov eax, coef_ptr - movups xmm2, [eax] - - mov eax, hist1_ptr - movlps xmm3, [eax] - shufps xmm3, xmm3, 44h - // hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr - - mulps xmm2, xmm3 - - subss xmm1, xmm2 - // Rotate elements right - shufps xmm2, xmm2, 39h - subss xmm1, xmm2 - - // Store new_hist - movss DWORD PTR [eax], xmm1 - - // Rotate elements right - shufps xmm2, xmm2, 39h - addss xmm1, xmm2 - - // Rotate elements right - shufps xmm2, xmm2, 39h - addss xmm1, xmm2 - - // Store previous hist - movss DWORD PTR [eax+4], xmm3 - - movss output, xmm1 - } -#else - output = atti386_iir_filter_sse(&output, hist1_ptr, coef_ptr); -#endif - return output; -} - -float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr) { - float output; - - // 1st number of coefficients array is overall input scale factor, or filter gain - output = input * (*coef_ptr++); - - // I find it very sad that 3DNow requires twice as many instructions as Intel's SSE - // Intel does have the upper hand here. -#ifdef _MSC_VER - float tmp; - __asm { - movq mm1, output - mov ebx, coef_ptr - movq mm2, [ebx] - - mov eax, hist1_ptr; - movq mm3, [eax] - - pfmul mm2, mm3 - pfsub mm1, mm2 - - psrlq mm2, 32 - pfsub mm1, mm2 - - // Store new hist - movd tmp, mm1 - - add ebx, 8 - movq mm2, [ebx] - movq mm3, [eax] - - pfmul mm2, mm3 - pfadd mm1, mm2 - - psrlq mm2, 32 - pfadd mm1, mm2 - - push tmp - pop DWORD PTR [eax] - - movd DWORD PTR [eax+4], mm3 - - add ebx, 8 - add eax, 8 - - movq mm2, [ebx] - movq mm3, [eax] - - pfmul mm2, mm3 - pfsub mm1, mm2 - - psrlq mm2, 32 - pfsub mm1, mm2 - - // Store new hist - movd tmp, mm1 - - add ebx, 8 - movq mm2, [ebx] - movq mm3, [eax] - - pfmul mm2, mm3 - pfadd mm1, mm2 - - psrlq mm2, 32 - pfadd mm1, mm2 - - push tmp - pop DWORD PTR [eax] - movd DWORD PTR [eax+4], mm3 - - movd output, mm1 - - femms - } -#else - output = atti386_iir_filter_3DNow(output, hist1_ptr, coef_ptr); -#endif - return output; -} - -#if MT32EMU_USE_MMX > 0 - -int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf) { - int tmplen = len >> 1; - if (tmplen == 0) { - return 0; - } -#ifdef _MSC_VER - __asm { - mov ecx,tmplen - mov ax, leftvol - shl eax,16 - mov ax, rightvol - movd mm1, eax - movd mm2, eax - psllq mm1, 32 - por mm1, mm2 - mov edi, partialBuf - mov esi, mixedBuf -mmxloop1: - mov bx, [esi] - add esi,2 - mov dx, [esi] - add esi,2 - - mov ax, dx - shl eax, 16 - mov ax, dx - movd mm2,eax - psllq mm2, 32 - mov ax, bx - shl eax, 16 - mov ax, bx - movd mm3,eax - por mm2,mm3 - - pmulhw mm2, mm1 - movq [edi], mm2 - add edi, 8 - - dec ecx - cmp ecx,0 - jg mmxloop1 - emms - } -#else - atti386_partialProductOutput(tmplen, leftvol, rightvol, partialBuf, mixedBuf); -#endif - return tmplen << 1; -} - -int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { - int tmplen = len >> 2; - if (tmplen == 0) { - return 0; - } -#ifdef _MSC_VER - __asm { - mov ecx, tmplen - mov esi, buf1 - mov edi, buf2 - -mixloop1: - movq mm1, [edi] - movq mm2, [esi] - paddw mm1,mm2 - movq [esi],mm1 - add edi,8 - add esi,8 - - dec ecx - cmp ecx,0 - jg mixloop1 - emms - } -#else - atti386_mixBuffers(buf1, buf2, tmplen); -#endif - return tmplen << 2; -} - - -int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { - int tmplen = len >> 2; - if (tmplen == 0) { - return 0; - } -#ifdef _MSC_VER - __asm { - mov ecx, tmplen - mov esi, buf1 - mov edi, buf2 - -mixloop2: - movq mm1, [esi] - movq mm2, [edi] - movq mm3, mm1 - pmulhw mm1, mm2 - paddw mm1,mm3 - movq [esi],mm1 - add edi,8 - add esi,8 - - dec ecx - cmp ecx,0 - jg mixloop2 - emms - } -#else - atti386_mixBuffersRingMix(buf1, buf2, tmplen); -#endif - return tmplen << 2; -} - -int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { - int tmplen = len >> 2; - if (tmplen == 0) { - return 0; - } -#ifdef _MSC_VER - __asm { - mov ecx, tmplen - mov esi, buf1 - mov edi, buf2 - -mixloop3: - movq mm1, [esi] - movq mm2, [edi] - pmulhw mm1, mm2 - movq [esi],mm1 - add edi,8 - add esi,8 - - dec ecx - cmp ecx,0 - jg mixloop3 - emms - } -#else - atti386_mixBuffersRing(buf1, buf2, tmplen); -#endif - return tmplen << 2; -} - -int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) { - int tmplen = (len >> 1); - if (tmplen == 0) { - return 0; - } -#ifdef _MSC_VER - __asm { - mov ecx, tmplen - mov ax,volume - shl eax,16 - mov ax,volume - movd mm3,eax - movd mm2,eax - psllq mm3, 32 - por mm3,mm2 - mov esi, useBuf - mov edi, stream -mixloop4: - movq mm1, [esi] - movq mm2, [edi] - pmulhw mm1, mm3 - paddw mm1,mm2 - movq [edi], mm1 - - add esi,8 - add edi,8 - - dec ecx - cmp ecx,0 - jg mixloop4 - emms - } -#else - atti386_produceOutput1(tmplen, volume, useBuf, stream); -#endif - return tmplen << 1; -} - -#endif - -} - -#endif diff --git a/sound/softsynth/mt32/i386.h b/sound/softsynth/mt32/i386.h deleted file mode 100644 index e8644411cd..0000000000 --- a/sound/softsynth/mt32/i386.h +++ /dev/null @@ -1,49 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef MT32EMU_I386_H -#define MT32EMU_I386_H - -namespace MT32Emu { -#ifdef MT32EMU_HAVE_X86 - -// Function that detects the availablity of SSE SIMD instructions -bool DetectSIMD(); -// Function that detects the availablity of 3DNow instructions -bool Detect3DNow(); - -float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr); -float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr); -float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr); - -#if MT32EMU_USE_MMX > 0 -int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf); -int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len); -int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len); -int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len); -int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume); -#endif - -#endif - -} - -#endif diff --git a/sound/softsynth/mt32/module.mk b/sound/softsynth/mt32/module.mk deleted file mode 100644 index 4d5d899ac3..0000000000 --- a/sound/softsynth/mt32/module.mk +++ /dev/null @@ -1,14 +0,0 @@ -MODULE := sound/softsynth/mt32 - -MODULE_OBJS := \ - mt32_file.o \ - i386.o \ - part.o \ - partial.o \ - partialManager.o \ - synth.o \ - tables.o \ - freeverb.o - -# Include common rules -include $(srcdir)/rules.mk diff --git a/sound/softsynth/mt32/mt32_file.cpp b/sound/softsynth/mt32/mt32_file.cpp deleted file mode 100644 index cdf9fa13f6..0000000000 --- a/sound/softsynth/mt32/mt32_file.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - - -#include "mt32emu.h" - -namespace MT32Emu { - -bool File::readBit16u(Bit16u *in) { - Bit8u b[2]; - if (read(&b[0], 2) != 2) - return false; - *in = ((b[0] << 8) | b[1]); - return true; -} - -bool File::readBit32u(Bit32u *in) { - Bit8u b[4]; - if (read(&b[0], 4) != 4) - return false; - *in = ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]); - return true; -} - -bool File::writeBit16u(Bit16u out) { - if (!writeBit8u((Bit8u)((out & 0xFF00) >> 8))) { - return false; - } - if (!writeBit8u((Bit8u)(out & 0x00FF))) { - return false; - } - return true; -} - -bool File::writeBit32u(Bit32u out) { - if (!writeBit8u((Bit8u)((out & 0xFF000000) >> 24))) { - return false; - } - if (!writeBit8u((Bit8u)((out & 0x00FF0000) >> 16))) { - return false; - } - if (!writeBit8u((Bit8u)((out & 0x0000FF00) >> 8))) { - return false; - } - if (!writeBit8u((Bit8u)(out & 0x000000FF))) { - return false; - } - return true; -} - -} // End of namespace MT32Emu - diff --git a/sound/softsynth/mt32/mt32_file.h b/sound/softsynth/mt32/mt32_file.h deleted file mode 100644 index e6641660ee..0000000000 --- a/sound/softsynth/mt32/mt32_file.h +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef MT32EMU_FILE_H -#define MT32EMU_FILE_H - -#include "common/scummsys.h" - -namespace MT32Emu { - -class File { -public: - enum OpenMode { - OpenMode_read = 0, - OpenMode_write = 1 - }; - virtual ~File() {} - virtual void close() = 0; - virtual size_t read(void *in, size_t size) = 0; - virtual bool readBit8u(Bit8u *in) = 0; - virtual bool readBit16u(Bit16u *in); - virtual bool readBit32u(Bit32u *in); - virtual size_t write(const void *out, size_t size) = 0; - virtual bool writeBit8u(Bit8u out) = 0; - // Note: May write a single byte to the file before failing - virtual bool writeBit16u(Bit16u out); - // Note: May write some (<4) bytes to the file before failing - virtual bool writeBit32u(Bit32u out); - virtual bool isEOF() = 0; -}; - -} // End of namespace MT32Emu - -#endif diff --git a/sound/softsynth/mt32/mt32emu.h b/sound/softsynth/mt32/mt32emu.h deleted file mode 100644 index 6eedf04bc0..0000000000 --- a/sound/softsynth/mt32/mt32emu.h +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef MT32EMU_MT32EMU_H -#define MT32EMU_MT32EMU_H - -// Debugging -// Show the instruments played -#define MT32EMU_MONITOR_INSTRUMENTS 1 -// Shows number of partials MT-32 is playing, and on which parts -#define MT32EMU_MONITOR_PARTIALS 0 -// Determines how the waveform cache file is handled (must be regenerated after sampling rate change) -#define MT32EMU_WAVECACHEMODE 0 // Load existing cache if possible, otherwise generate and save cache -//#define MT32EMU_WAVECACHEMODE 1 // Load existing cache if possible, otherwise generate but don't save cache -//#define MT32EMU_WAVECACHEMODE 2 // Ignore existing cache, generate and save cache -//#define MT32EMU_WAVECACHEMODE 3 // Ignore existing cache, generate but don't save cache - -// Configuration -// The maximum number of partials playing simultaneously -#define MT32EMU_MAX_PARTIALS 32 -// The maximum number of notes playing simultaneously per part. -// No point making it more than MT32EMU_MAX_PARTIALS, since each note needs at least one partial. -#define MT32EMU_MAX_POLY 32 -// This calculates the exact frequencies of notes as they are played, instead of offsetting from pre-cached semitones. Potentially very slow. -#define MT32EMU_ACCURATENOTES 0 - -#if (defined (_MSC_VER) && defined(_M_IX86)) -#define MT32EMU_HAVE_X86 -#elif defined(__GNUC__) -#if __GNUC__ >= 3 && defined(__i386__) -#define MT32EMU_HAVE_X86 -#endif -#endif - -#ifdef MT32EMU_HAVE_X86 -#define MT32EMU_USE_MMX 1 -#else -#define MT32EMU_USE_MMX 0 -#endif - -#include "freeverb.h" - -#include "structures.h" -#include "i386.h" -#include "mt32_file.h" -#include "tables.h" -#include "partial.h" -#include "partialManager.h" -#include "part.h" -#include "synth.h" - -#endif diff --git a/sound/softsynth/mt32/part.cpp b/sound/softsynth/mt32/part.cpp deleted file mode 100644 index eb087f7ea0..0000000000 --- a/sound/softsynth/mt32/part.cpp +++ /dev/null @@ -1,633 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include -#include - -#include "mt32emu.h" - -namespace MT32Emu { - -static const Bit8u PartialStruct[13] = { - 0, 0, 2, 2, 1, 3, - 3, 0, 3, 0, 2, 1, 3 }; - -static const Bit8u PartialMixStruct[13] = { - 0, 1, 0, 1, 1, 0, - 1, 3, 3, 2, 2, 2, 2 }; - -static const float floatKeyfollow[17] = { - -1.0f, -1.0f/2.0f, -1.0f/4.0f, 0.0f, - 1.0f/8.0f, 1.0f/4.0f, 3.0f/8.0f, 1.0f/2.0f, 5.0f/8.0f, 3.0f/4.0f, 7.0f/8.0f, 1.0f, - 5.0f/4.0f, 3.0f/2.0f, 2.0f, - 1.0009765625f, 1.0048828125f -}; - -//FIXME:KG: Put this dpoly stuff somewhere better -bool dpoly::isActive() const { - return partials[0] != NULL || partials[1] != NULL || partials[2] != NULL || partials[3] != NULL; -} - -Bit32u dpoly::getAge() const { - for (int i = 0; i < 4; i++) { - if (partials[i] != NULL) { - return partials[i]->age; - } - } - return 0; -} - -RhythmPart::RhythmPart(Synth *useSynth, unsigned int usePartNum): Part(useSynth, usePartNum) { - strcpy(name, "Rhythm"); - rhythmTemp = &synth->mt32ram.rhythmSettings[0]; - refresh(); -} - -Part::Part(Synth *useSynth, unsigned int usePartNum) { - this->synth = useSynth; - this->partNum = usePartNum; - patchCache[0].dirty = true; - holdpedal = false; - patchTemp = &synth->mt32ram.patchSettings[partNum]; - if (usePartNum == 8) { - // Nasty hack for rhythm - timbreTemp = NULL; - } else { - sprintf(name, "Part %d", partNum + 1); - timbreTemp = &synth->mt32ram.timbreSettings[partNum]; - } - currentInstr[0] = 0; - currentInstr[10] = 0; - expression = 127; - volumeMult = 0; - volumesetting.leftvol = 32767; - volumesetting.rightvol = 32767; - bend = 0.0f; - memset(polyTable,0,sizeof(polyTable)); - memset(patchCache, 0, sizeof(patchCache)); -} - -void Part::setHoldPedal(bool pedalval) { - if (holdpedal && !pedalval) { - holdpedal = false; - stopPedalHold(); - } else { - holdpedal = pedalval; - } -} - -void RhythmPart::setBend(unsigned int midiBend) { - synth->printDebug("%s: Setting bend (%d) not supported on rhythm", name, midiBend); - return; -} - -void Part::setBend(unsigned int midiBend) { - // FIXME:KG: Slightly unbalanced increments, but I wanted min -1.0, centre 0.0 and max 1.0 - if (midiBend <= 0x2000) { - bend = ((signed int)midiBend - 0x2000) / (float)0x2000; - } else { - bend = ((signed int)midiBend - 0x2000) / (float)0x1FFF; - } - // Loop through all partials to update their bend - for (int i = 0; i < MT32EMU_MAX_POLY; i++) { - for (int j = 0; j < 4; j++) { - if (polyTable[i].partials[j] != NULL) { - polyTable[i].partials[j]->setBend(bend); - } - } - } -} - -void RhythmPart::setModulation(unsigned int midiModulation) { - synth->printDebug("%s: Setting modulation (%d) not supported on rhythm", name, midiModulation); -} - -void Part::setModulation(unsigned int midiModulation) { - // Just a bloody guess, as always, before I get things figured out - for (int t = 0; t < 4; t++) { - if (patchCache[t].playPartial) { - int newrate = (patchCache[t].modsense * midiModulation) >> 7; - //patchCache[t].lfoperiod = lfotable[newrate]; - patchCache[t].lfodepth = newrate; - //FIXME:KG: timbreTemp->partial[t].lfo.depth = - } - } -} - -void RhythmPart::refresh() { - updateVolume(); - // (Re-)cache all the mapped timbres ahead of time - for (unsigned int drumNum = 0; drumNum < synth->controlROMMap->rhythmSettingsCount; drumNum++) { - int drumTimbreNum = rhythmTemp[drumNum].timbre; - if (drumTimbreNum >= 127) // 94 on MT-32 - continue; - Bit16s pan = rhythmTemp[drumNum].panpot; // They use R-L 0-14... - // FIXME:KG: Panning cache should be backed up to partials using it, too - if (pan < 7) { - drumPan[drumNum].leftvol = pan * 4681; - drumPan[drumNum].rightvol = 32767; - } else { - drumPan[drumNum].rightvol = (14 - pan) * 4681; - drumPan[drumNum].leftvol = 32767; - } - PatchCache *cache = drumCache[drumNum]; - backupCacheToPartials(cache); - for (int t = 0; t < 4; t++) { - // Common parameters, stored redundantly - cache[t].dirty = true; - cache[t].pitchShift = 0.0f; - cache[t].benderRange = 0.0f; - cache[t].pansetptr = &drumPan[drumNum]; - cache[t].reverb = rhythmTemp[drumNum].reverbSwitch > 0; - } - } -} - -void Part::refresh() { - updateVolume(); - backupCacheToPartials(patchCache); - for (int t = 0; t < 4; t++) { - // Common parameters, stored redundantly - patchCache[t].dirty = true; - patchCache[t].pitchShift = (patchTemp->patch.keyShift - 24) + (patchTemp->patch.fineTune - 50) / 100.0f; - patchCache[t].benderRange = patchTemp->patch.benderRange; - patchCache[t].pansetptr = &volumesetting; - patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0; - } - memcpy(currentInstr, timbreTemp->common.name, 10); -} - -const char *Part::getCurrentInstr() const { - return ¤tInstr[0]; -} - -void RhythmPart::refreshTimbre(unsigned int absTimbreNum) { - for (int m = 0; m < 85; m++) { - if (rhythmTemp[m].timbre == absTimbreNum - 128) - drumCache[m][0].dirty = true; - } -} - -void Part::refreshTimbre(unsigned int absTimbreNum) { - if (getAbsTimbreNum() == absTimbreNum) { - memcpy(currentInstr, timbreTemp->common.name, 10); - patchCache[0].dirty = true; - } -} - -int Part::fixBiaslevel(int srcpnt, int *dir) { - int noteat = srcpnt & 0x3F; - int outnote; - if (srcpnt < 64) - *dir = 0; - else - *dir = 1; - outnote = 33 + noteat; - //synth->printDebug("Bias note %d, dir %d", outnote, *dir); - - return outnote; -} - -int Part::fixKeyfollow(int srckey) { - if (srckey>=0 && srckey<=16) { - int keyfix[17] = { -256*16, -128*16, -64*16, 0, 32*16, 64*16, 96*16, 128*16, (128+32)*16, 192*16, (192+32)*16, 256*16, (256+64)*16, (256+128)*16, (512)*16, 4100, 4116}; - return keyfix[srckey]; - } else { - //LOG(LOG_ERROR|LOG_MISC,"Missed key: %d", srckey); - return 256; - } -} - -void Part::abortPoly(dpoly *poly) { - if (!poly->isPlaying) { - return; - } - for (int i = 0; i < 4; i++) { - Partial *partial = poly->partials[i]; - if (partial != NULL) { - partial->deactivate(); - } - } - poly->isPlaying = false; -} - -void Part::setPatch(const PatchParam *patch) { - patchTemp->patch = *patch; -} - -void RhythmPart::setTimbre(TimbreParam * /*timbre*/) { - synth->printDebug("%s: Attempted to call setTimbre() - doesn't make sense for rhythm", name); -} - -void Part::setTimbre(TimbreParam *timbre) { - *timbreTemp = *timbre; -} - -unsigned int RhythmPart::getAbsTimbreNum() const { - synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm", name); - return 0; -} - -unsigned int Part::getAbsTimbreNum() const { - return (patchTemp->patch.timbreGroup * 64) + patchTemp->patch.timbreNum; -} - -void RhythmPart::setProgram(unsigned int patchNum) { - synth->printDebug("%s: Attempt to set program (%d) on rhythm is invalid", name, patchNum); -} - -void Part::setProgram(unsigned int patchNum) { - setPatch(&synth->mt32ram.patches[patchNum]); - setTimbre(&synth->mt32ram.timbres[getAbsTimbreNum()].timbre); - - refresh(); - - allSoundOff(); //FIXME:KG: Is this correct? -} - -void Part::backupCacheToPartials(PatchCache cache[4]) { - // check if any partials are still playing with the old patch cache - // if so then duplicate the cached data from the part to the partial so that - // we can change the part's cache without affecting the partial. - // We delay this until now to avoid a copy operation with every note played - for (int m = 0; m < MT32EMU_MAX_POLY; m++) { - for (int i = 0; i < 4; i++) { - Partial *partial = polyTable[m].partials[i]; - if (partial != NULL && partial->patchCache == &cache[i]) { - partial->cachebackup = cache[i]; - partial->patchCache = &partial->cachebackup; - } - } - } -} - -void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) { - backupCacheToPartials(cache); - int partialCount = 0; - for (int t = 0; t < 4; t++) { - cache[t].PCMPartial = false; - if (((timbre->common.pmute >> t) & 0x1) == 1) { - cache[t].playPartial = true; - partialCount++; - } else { - cache[t].playPartial = false; - continue; - } - - // Calculate and cache common parameters - - cache[t].pcm = timbre->partial[t].wg.pcmwave; - cache[t].useBender = (timbre->partial[t].wg.bender == 1); - - switch (t) { - case 0: - cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x2) ? true : false; - cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; - cache[t].structurePosition = 0; - cache[t].structurePair = 1; - break; - case 1: - cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x1) ? true : false; - cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; - cache[t].structurePosition = 1; - cache[t].structurePair = 0; - break; - case 2: - cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x2) ? true : false; - cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; - cache[t].structurePosition = 0; - cache[t].structurePair = 3; - break; - case 3: - cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x1) ? true : false; - cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; - cache[t].structurePosition = 1; - cache[t].structurePair = 2; - break; - default: - break; - } - - cache[t].waveform = timbre->partial[t].wg.waveform; - cache[t].pulsewidth = timbre->partial[t].wg.pulsewid; - cache[t].pwsens = timbre->partial[t].wg.pwvelo; - if (timbre->partial[t].wg.keyfollow > 16) { - synth->printDebug("Bad keyfollow value in timbre!"); - cache[t].pitchKeyfollow = 1.0f; - } else { - cache[t].pitchKeyfollow = floatKeyfollow[timbre->partial[t].wg.keyfollow]; - } - - cache[t].pitch = timbre->partial[t].wg.coarse + (timbre->partial[t].wg.fine - 50) / 100.0f + 24.0f; - cache[t].pitchEnv = timbre->partial[t].env; - cache[t].pitchEnv.sensitivity = (char)((float)cache[t].pitchEnv.sensitivity * 1.27f); - cache[t].pitchsustain = cache[t].pitchEnv.level[3]; - - // Calculate and cache TVA envelope stuff - cache[t].ampEnv = timbre->partial[t].tva; - cache[t].ampEnv.level = (char)((float)cache[t].ampEnv.level * 1.27f); - - cache[t].ampbias[0] = fixBiaslevel(cache[t].ampEnv.biaspoint1, &cache[t].ampdir[0]); - cache[t].ampblevel[0] = 12 - cache[t].ampEnv.biaslevel1; - cache[t].ampbias[1] = fixBiaslevel(cache[t].ampEnv.biaspoint2, &cache[t].ampdir[1]); - cache[t].ampblevel[1] = 12 - cache[t].ampEnv.biaslevel2; - cache[t].ampdepth = cache[t].ampEnv.envvkf * cache[t].ampEnv.envvkf; - - // Calculate and cache filter stuff - cache[t].filtEnv = timbre->partial[t].tvf; - cache[t].filtkeyfollow = fixKeyfollow(cache[t].filtEnv.keyfollow); - cache[t].filtEnv.envdepth = (char)((float)cache[t].filtEnv.envdepth * 1.27); - cache[t].tvfbias = fixBiaslevel(cache[t].filtEnv.biaspoint, &cache[t].tvfdir); - cache[t].tvfblevel = cache[t].filtEnv.biaslevel; - cache[t].filtsustain = cache[t].filtEnv.envlevel[3]; - - // Calculate and cache LFO stuff - cache[t].lfodepth = timbre->partial[t].lfo.depth; - cache[t].lfoperiod = synth->tables.lfoPeriod[(int)timbre->partial[t].lfo.rate]; - cache[t].lforate = timbre->partial[t].lfo.rate; - cache[t].modsense = timbre->partial[t].lfo.modsense; - } - for (int t = 0; t < 4; t++) { - // Common parameters, stored redundantly - cache[t].dirty = false; - cache[t].partialCount = partialCount; - cache[t].sustain = (timbre->common.nosustain == 0); - } - //synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", cache[0].waveform, cache[1].waveform, cache[2].waveform, cache[3].waveform); - -#if MT32EMU_MONITOR_INSTRUMENTS == 1 - synth->printDebug("%s (%s): Recached timbre", name, currentInstr); - for (int i = 0; i < 4; i++) { - synth->printDebug(" %d: play=%s, pcm=%s (%d), wave=%d", i, cache[i].playPartial ? "YES" : "NO", cache[i].PCMPartial ? "YES" : "NO", timbre->partial[i].wg.pcmwave, timbre->partial[i].wg.waveform); - } -#endif -} - -const char *Part::getName() const { - return name; -} - -void Part::updateVolume() { - volumeMult = synth->tables.volumeMult[patchTemp->outlevel * expression / 127]; -} - -int Part::getVolume() const { - // FIXME: Use the mappings for this in the control ROM - return patchTemp->outlevel * 127 / 100; -} - -void Part::setVolume(int midiVolume) { - // FIXME: Use the mappings for this in the control ROM - patchTemp->outlevel = (Bit8u)(midiVolume * 100 / 127); - updateVolume(); - synth->printDebug("%s (%s): Set volume to %d", name, currentInstr, midiVolume); -} - -void Part::setExpression(int midiExpression) { - expression = midiExpression; - updateVolume(); -} - -void RhythmPart::setPan(unsigned int midiPan) -{ - // FIXME:KG: This is unchangeable for drums (they always use drumPan), is that correct? - synth->printDebug("%s: Setting pan (%d) not supported on rhythm", name, midiPan); -} - -void Part::setPan(unsigned int midiPan) { - // FIXME:KG: Tweaked this a bit so that we have a left 100%, centre and right 100% - // (But this makes the range somewhat skewed) - // Check against the real thing - // NOTE: Panning is inverted compared to GM. - if (midiPan < 64) { - volumesetting.leftvol = (Bit16s)(midiPan * 512); - volumesetting.rightvol = 32767; - } else if (midiPan == 64) { - volumesetting.leftvol = 32767; - volumesetting.rightvol = 32767; - } else { - volumesetting.rightvol = (Bit16s)((127 - midiPan) * 520); - volumesetting.leftvol = 32767; - } - patchTemp->panpot = (Bit8u)(midiPan * 14 / 127); - //synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot); -} - -void RhythmPart::playNote(unsigned int key, int vel) { - if (key < 24 || key > 108)/*> 87 on MT-32)*/ { - synth->printDebug("%s: Attempted to play invalid key %d", name, key); - return; - } - int drumNum = key - 24; - int drumTimbreNum = rhythmTemp[drumNum].timbre; - if (drumTimbreNum >= 127) { // 94 on MT-32 - synth->printDebug("%s: Attempted to play unmapped key %d", name, key); - return; - } - int absTimbreNum = drumTimbreNum + 128; - TimbreParam *timbre = &synth->mt32ram.timbres[absTimbreNum].timbre; - memcpy(currentInstr, timbre->common.name, 10); -#if MT32EMU_MONITOR_INSTRUMENTS == 1 - synth->printDebug("%s (%s): starting poly (drum %d, timbre %d) - Vel %d Key %d", name, currentInstr, drumNum, absTimbreNum, vel, key); -#endif - if (drumCache[drumNum][0].dirty) { - cacheTimbre(drumCache[drumNum], timbre); - } - playPoly(drumCache[drumNum], key, MIDDLEC, vel); -} - -void Part::playNote(unsigned int key, int vel) { - int freqNum = key; - if (freqNum < 12) { - synth->printDebug("%s (%s): Attempted to play invalid key %d < 12; moving up by octave", name, currentInstr, key); - freqNum += 12; - } else if (freqNum > 108) { - synth->printDebug("%s (%s): Attempted to play invalid key %d > 108; moving down by octave", name, currentInstr, key); - while (freqNum > 108) { - freqNum -= 12; - } - } - // POLY1 mode, Single Assign - // Haven't found any software that uses any of the other poly modes - // FIXME:KG: Should this also apply to rhythm? - for (unsigned int i = 0; i < MT32EMU_MAX_POLY; i++) { - if (polyTable[i].isActive() && (polyTable[i].key == key)) { - //AbortPoly(&polyTable[i]); - stopNote(key); - break; - } - } -#if MT32EMU_MONITOR_INSTRUMENTS == 1 - synth->printDebug("%s (%s): starting poly - Vel %d Key %d", name, currentInstr, vel, key); -#endif - if (patchCache[0].dirty) { - cacheTimbre(patchCache, timbreTemp); - } - playPoly(patchCache, key, freqNum, vel); -} - -void Part::playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel) { - unsigned int needPartials = cache[0].partialCount; - unsigned int freePartials = synth->partialManager->getFreePartialCount(); - - if (freePartials < needPartials) { - if (!synth->partialManager->freePartials(needPartials - freePartials, partNum)) { - synth->printDebug("%s (%s): Insufficient free partials to play key %d (vel=%d); needed=%d, free=%d", name, currentInstr, key, vel, needPartials, synth->partialManager->getFreePartialCount()); - return; - } - } - // Find free poly - int m; - for (m = 0; m < MT32EMU_MAX_POLY; m++) { - if (!polyTable[m].isActive()) { - break; - } - } - if (m == MT32EMU_MAX_POLY) { - synth->printDebug("%s (%s): No free poly to play key %d (vel %d)", name, currentInstr, key, vel); - return; - } - - dpoly *tpoly = &polyTable[m]; - - tpoly->isPlaying = true; - tpoly->key = key; - tpoly->isDecay = false; - tpoly->freqnum = freqNum; - tpoly->vel = vel; - tpoly->pedalhold = false; - - bool allnull = true; - for (int x = 0; x < 4; x++) { - if (cache[x].playPartial) { - tpoly->partials[x] = synth->partialManager->allocPartial(partNum); - allnull = false; - } else { - tpoly->partials[x] = NULL; - } - } - - if (allnull) - synth->printDebug("%s (%s): No partials to play for this instrument", name, this->currentInstr); - - tpoly->sustain = cache[0].sustain; - tpoly->volumeptr = &volumeMult; - - for (int x = 0; x < 4; x++) { - if (tpoly->partials[x] != NULL) { - tpoly->partials[x]->startPartial(tpoly, &cache[x], tpoly->partials[cache[x].structurePair]); - tpoly->partials[x]->setBend(bend); - } - } -} - -static void startDecayPoly(dpoly *tpoly) { - if (tpoly->isDecay) { - return; - } - tpoly->isDecay = true; - - for (int t = 0; t < 4; t++) { - Partial *partial = tpoly->partials[t]; - if (partial == NULL) - continue; - partial->startDecayAll(); - } - tpoly->isPlaying = false; -} - -void Part::allNotesOff() { - // Note: Unchecked on real MT-32, but the MIDI specification states that all notes off (0x7B) - // should treat the hold pedal as usual. - // All *sound* off (0x78) should stop notes immediately regardless of the hold pedal. - // The latter controller is not implemented on the MT-32 (according to the docs). - for (int q = 0; q < MT32EMU_MAX_POLY; q++) { - dpoly *tpoly = &polyTable[q]; - if (tpoly->isPlaying) { - if (holdpedal) - tpoly->pedalhold = true; - else if (tpoly->sustain) - startDecayPoly(tpoly); - } - } -} - -void Part::allSoundOff() { - for (int q = 0; q < MT32EMU_MAX_POLY; q++) { - dpoly *tpoly = &polyTable[q]; - if (tpoly->isPlaying) { - startDecayPoly(tpoly); - } - } -} - -void Part::stopPedalHold() { - for (int q = 0; q < MT32EMU_MAX_POLY; q++) { - dpoly *tpoly; - tpoly = &polyTable[q]; - if (tpoly->isActive() && tpoly->pedalhold) - stopNote(tpoly->key); - } -} - -void Part::stopNote(unsigned int key) { - // Non-sustaining instruments ignore stop commands. - // They die away eventually anyway - -#if MT32EMU_MONITOR_INSTRUMENTS == 1 - synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key); -#endif - - if (key != 255) { - for (int q = 0; q < MT32EMU_MAX_POLY; q++) { - dpoly *tpoly = &polyTable[q]; - if (tpoly->isPlaying && tpoly->key == key) { - if (holdpedal) - tpoly->pedalhold = true; - else if (tpoly->sustain) - startDecayPoly(tpoly); - } - } - return; - } - - // Find oldest poly... yes, the MT-32 can be reconfigured to kill different poly first - // This is simplest - int oldest = -1; - Bit32u oldage = 0; - - for (int q = 0; q < MT32EMU_MAX_POLY; q++) { - dpoly *tpoly = &polyTable[q]; - - if (tpoly->isPlaying && !tpoly->isDecay) { - if (tpoly->getAge() >= oldage) { - oldage = tpoly->getAge(); - oldest = q; - } - } - } - - if (oldest != -1) { - startDecayPoly(&polyTable[oldest]); - } -} - -} diff --git a/sound/softsynth/mt32/part.h b/sound/softsynth/mt32/part.h deleted file mode 100644 index 54c4999653..0000000000 --- a/sound/softsynth/mt32/part.h +++ /dev/null @@ -1,113 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef MT32EMU_PART_H -#define MT32EMU_PART_H - -namespace MT32Emu { - -class PartialManager; -class Synth; - -class Part { -private: - // Pointers to the areas of the MT-32's memory dedicated to this part (for parts 1-8) - MemParams::PatchTemp *patchTemp; - TimbreParam *timbreTemp; - - // 0=Part 1, .. 7=Part 8, 8=Rhythm - unsigned int partNum; - - bool holdpedal; - - StereoVolume volumesetting; - - PatchCache patchCache[4]; - - float bend; // -1.0 .. +1.0 - - dpoly polyTable[MT32EMU_MAX_POLY]; - - void abortPoly(dpoly *poly); - - static int fixKeyfollow(int srckey); - static int fixBiaslevel(int srcpnt, int *dir); - - void setPatch(const PatchParam *patch); - -protected: - Synth *synth; - char name[8]; // "Part 1".."Part 8", "Rhythm" - char currentInstr[11]; - int expression; - Bit32u volumeMult; - - void updateVolume(); - void backupCacheToPartials(PatchCache cache[4]); - void cacheTimbre(PatchCache cache[4], const TimbreParam *timbre); - void playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel); - const char *getName() const; - -public: - Part(Synth *synth, unsigned int usePartNum); - virtual ~Part() {} - virtual void playNote(unsigned int key, int vel); - void stopNote(unsigned int key); - void allNotesOff(); - void allSoundOff(); - int getVolume() const; - void setVolume(int midiVolume); - void setExpression(int midiExpression); - virtual void setPan(unsigned int midiPan); - virtual void setBend(unsigned int midiBend); - virtual void setModulation(unsigned int midiModulation); - virtual void setProgram(unsigned int midiProgram); - void setHoldPedal(bool pedalval); - void stopPedalHold(); - virtual void refresh(); - virtual void refreshTimbre(unsigned int absTimbreNum); - virtual void setTimbre(TimbreParam *timbre); - virtual unsigned int getAbsTimbreNum() const; - const char *getCurrentInstr() const; -}; - -class RhythmPart: public Part { - // Pointer to the area of the MT-32's memory dedicated to rhythm - const MemParams::RhythmTemp *rhythmTemp; - - // This caches the timbres/settings in use by the rhythm part - PatchCache drumCache[85][4]; - StereoVolume drumPan[85]; -public: - RhythmPart(Synth *synth, unsigned int usePartNum); - void refresh(); - void refreshTimbre(unsigned int timbreNum); - void setTimbre(TimbreParam *timbre); - void playNote(unsigned int key, int vel); - unsigned int getAbsTimbreNum() const; - void setPan(unsigned int midiPan); - void setBend(unsigned int midiBend); - void setModulation(unsigned int midiModulation); - void setProgram(unsigned int patchNum); -}; - -} -#endif diff --git a/sound/softsynth/mt32/partial.cpp b/sound/softsynth/mt32/partial.cpp deleted file mode 100644 index 5ba9ef6145..0000000000 --- a/sound/softsynth/mt32/partial.cpp +++ /dev/null @@ -1,968 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include -#include -#include - -#include "mt32emu.h" - -#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__) -// Older versions of Mac OS X didn't supply a powf function, so using it -// will cause a binary incompatibility when trying to run a binary built -// on a newer OS X release on an olderr one. And Solaris 8 doesn't provide -// powf, floorf, fabsf etc. at all. -// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in -// libstdc++. math/stubs.o should be empty, but it comes with a symbol for -// powf, resulting in a linker error because of multiple definitions. -// Hence we re-define them here. The only potential drawback is that it -// might be a little bit slower this way. -#define powf(x,y) ((float)pow(x,y)) -#define floorf(x) ((float)floor(x)) -#define fabsf(x) ((float)fabs(x)) -#endif - -#define FIXEDPOINT_UDIV(x, y, point) (((x) << (point)) / ((y))) -#define FIXEDPOINT_SDIV(x, y, point) (((x) * (1 << point)) / ((y))) -#define FIXEDPOINT_UMULT(x, y, point) (((x) * (y)) >> point) -#define FIXEDPOINT_SMULT(x, y, point) (((x) * (y)) / (1 << point)) - -using namespace MT32Emu; - -Partial::Partial(Synth *useSynth) { - this->synth = useSynth; - ownerPart = -1; - poly = NULL; - pair = NULL; -#if MT32EMU_ACCURATENOTES == 1 - for (int i = 0; i < 3; i++) { - noteLookupStorage.waveforms[i] = new Bit16s[65536]; - } - noteLookup = ¬eLookupStorage; -#endif -} - -Partial::~Partial() { -#if MT32EMU_ACCURATENOTES == 1 - for (int i = 0; i < 3; i++) { - delete[] noteLookupStorage.waveforms[i]; - } - delete[] noteLookupStorage.wavTable; -#endif -} - -int Partial::getOwnerPart() const { - return ownerPart; -} - -bool Partial::isActive() { - return ownerPart > -1; -} - -const dpoly *Partial::getDpoly() const { - return this->poly; -} - -void Partial::activate(int part) { - // This just marks the partial as being assigned to a part - ownerPart = part; -} - -void Partial::deactivate() { - ownerPart = -1; - if (poly != NULL) { - for (int i = 0; i < 4; i++) { - if (poly->partials[i] == this) { - poly->partials[i] = NULL; - break; - } - } - if (pair != NULL) { - pair->pair = NULL; - } - } -} - -void Partial::initKeyFollow(int key) { - // Setup partial keyfollow - // Note follow relative to middle C - - // Calculate keyfollow for pitch -#if 1 - float rel = key == -1 ? 0.0f : (key - MIDDLEC); - float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch + patchCache->pitchShift; - //FIXME:KG: Does it truncate the keyfollowed pitch to a semitone (towards MIDDLEC)? - //int newKey = (int)(rel * patchCache->pitchKeyfollow); - //float newPitch = newKey + patchCache->pitch + patchCache->pitchShift; -#else - float rel = key == -1 ? 0.0f : (key + patchCache->pitchShift - MIDDLEC); - float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch; -#endif -#if MT32EMU_ACCURATENOTES == 1 - noteVal = newPitch; - synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f", key, (double)patchCache->pitch, (double)patchCache->pitchKeyfollow, (double)patchCache->pitchShift, (double)newPitch); -#else - float newPitchInt; - float newPitchFract = modff(newPitch, &newPitchInt); - if (newPitchFract > 0.5f) { - newPitchInt += 1.0f; - newPitchFract -= 1.0f; - } - noteVal = (int)newPitchInt; - fineShift = (int)(powf(2.0f, newPitchFract / 12.0f) * 4096.0f); - synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f, noteVal=%d, fineShift=%d", key, (double)patchCache->pitch, (double)patchCache->pitchKeyfollow, (double)patchCache->pitchShift, (double)newPitch, noteVal, fineShift); -#endif - // FIXME:KG: Raise/lower by octaves until in the supported range. - while (noteVal > HIGHEST_NOTE) // FIXME:KG: see tables.cpp: >108? - noteVal -= 12; - while (noteVal < LOWEST_NOTE) // FIXME:KG: see tables.cpp: <12? - noteVal += 12; - // Calculate keyfollow for filter - int keyfollow = ((key - MIDDLEC) * patchCache->filtkeyfollow) / 4096; - if (keyfollow > 108) - keyfollow = 108; - else if (keyfollow < -108) - keyfollow = -108; - filtVal = synth->tables.tvfKeyfollowMult[keyfollow + 108]; - realVal = synth->tables.tvfKeyfollowMult[(noteVal - MIDDLEC) + 108]; -} - -int Partial::getKey() const { - if (poly == NULL) { - return -1; - } else { - return poly->key; - } -} - -void Partial::startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial) { - if (usePoly == NULL || useCache == NULL) { - synth->printDebug("*** Error: Starting partial for owner %d, usePoly=%s, useCache=%s", ownerPart, usePoly == NULL ? "*** NULL ***" : "OK", useCache == NULL ? "*** NULL ***" : "OK"); - return; - } - patchCache = useCache; - poly = usePoly; - mixType = patchCache->structureMix; - structurePosition = patchCache->structurePosition; - - play = true; - initKeyFollow(poly->freqnum); // Initialises noteVal, filtVal and realVal -#if MT32EMU_ACCURATENOTES == 0 - noteLookup = &synth->tables.noteLookups[noteVal - LOWEST_NOTE]; -#else - Tables::initNote(synth, ¬eLookupStorage, noteVal, (float)synth->myProp.sampleRate, synth->masterTune, synth->pcmWaves, NULL); -#endif - keyLookup = &synth->tables.keyLookups[poly->freqnum - 12]; - - if (patchCache->PCMPartial) { - pcmNum = patchCache->pcm; - if (synth->controlROMMap->pcmCount > 128) { - // CM-32L, etc. support two "banks" of PCMs, selectable by waveform type parameter. - if (patchCache->waveform > 1) { - pcmNum += 128; - } - } - pcmWave = &synth->pcmWaves[pcmNum]; - } else { - pcmWave = NULL; - } - - lfoPos = 0; - pulsewidth = patchCache->pulsewidth + synth->tables.pwVelfollowAdd[patchCache->pwsens][poly->vel]; - if (pulsewidth > 100) { - pulsewidth = 100; - } else if (pulsewidth < 0) { - pulsewidth = 0; - } - - for (int e = 0; e < 3; e++) { - envs[e].envpos = 0; - envs[e].envstat = -1; - envs[e].envbase = 0; - envs[e].envdist = 0; - envs[e].envsize = 0; - envs[e].sustaining = false; - envs[e].decaying = false; - envs[e].prevlevel = 0; - envs[e].counter = 0; - envs[e].count = 0; - } - ampEnvVal = 0; - pitchEnvVal = 0; - pitchSustain = false; - loopPos = 0; - partialOff.pcmoffset = partialOff.pcmplace = 0; - pair = pairPartial; - useNoisePair = pairPartial == NULL && (mixType == 1 || mixType == 2); - age = 0; - alreadyOutputed = false; - memset(history,0,sizeof(history)); -} - -Bit16s *Partial::generateSamples(long length) { - if (!isActive() || alreadyOutputed) { - return NULL; - } - if (poly == NULL) { - synth->printDebug("*** ERROR: poly is NULL at Partial::generateSamples()!"); - return NULL; - } - - alreadyOutputed = true; - - // Generate samples - - Bit16s *partialBuf = &myBuffer[0]; - Bit32u volume = *poly->volumeptr; - while (length--) { - Bit32s envval; - Bit32s sample = 0; - if (!envs[EnvelopeType_amp].sustaining) { - if (envs[EnvelopeType_amp].count <= 0) { - Bit32u ampval = getAmpEnvelope(); - if (!play) { - deactivate(); - break; - } - if (ampval > 100) { - ampval = 100; - } - - ampval = synth->tables.volumeMult[ampval]; - ampval = FIXEDPOINT_UMULT(ampval, synth->tables.tvaVelfollowMult[poly->vel][(int)patchCache->ampEnv.velosens], 8); - //if (envs[EnvelopeType_amp].sustaining) - ampEnvVal = ampval; - } - --envs[EnvelopeType_amp].count; - } - - unsigned int lfoShift = 0x1000; - if (pitchSustain) { - // Calculate LFO position - // LFO does not kick in completely until pitch envelope sustains - if (patchCache->lfodepth > 0) { - lfoPos++; - if (lfoPos >= patchCache->lfoperiod) - lfoPos = 0; - int lfoatm = FIXEDPOINT_UDIV(lfoPos, patchCache->lfoperiod, 16); - int lfoatr = synth->tables.sintable[lfoatm]; - lfoShift = synth->tables.lfoShift[patchCache->lfodepth][lfoatr]; - } - } else { - // Calculate Pitch envelope - envval = getPitchEnvelope(); - int pd = patchCache->pitchEnv.depth; - pitchEnvVal = synth->tables.pitchEnvVal[pd][envval]; - } - - int delta; - - // Wrap positions or end if necessary - if (patchCache->PCMPartial) { - // PCM partial - - delta = noteLookup->wavTable[pcmNum]; - int len = pcmWave->len; - if (partialOff.pcmplace >= len) { - if (pcmWave->loop) { - //partialOff.pcmplace = partialOff.pcmoffset = 0; - partialOff.pcmplace %= len; - } else { - play = false; - deactivate(); - break; - } - } - } else { - // Synthesis partial - delta = 0x10000; - partialOff.pcmplace %= (Bit16u)noteLookup->div2; - } - - // Build delta for position of next sample - // Fix delta code - Bit32u tdelta = delta; -#if MT32EMU_ACCURATENOTES == 0 - tdelta = FIXEDPOINT_UMULT(tdelta, fineShift, 12); -#endif - tdelta = FIXEDPOINT_UMULT(tdelta, pitchEnvVal, 12); - tdelta = FIXEDPOINT_UMULT(tdelta, lfoShift, 12); - tdelta = FIXEDPOINT_UMULT(tdelta, bendShift, 12); - delta = (int)tdelta; - - // Get waveform - either PCM or synthesized sawtooth or square - if (ampEnvVal > 0) { - if (patchCache->PCMPartial) { - // Render PCM sample - int ra, rb, dist; - Bit32u taddr; - Bit32u pcmAddr = pcmWave->addr; - if (delta < 0x10000) { - // Linear sound interpolation - taddr = pcmAddr + partialOff.pcmplace; - ra = synth->pcmROMData[taddr]; - taddr++; - if (taddr == pcmAddr + pcmWave->len) { - // Past end of PCM - if (pcmWave->loop) { - rb = synth->pcmROMData[pcmAddr]; - } else { - rb = 0; - } - } else { - rb = synth->pcmROMData[taddr]; - } - dist = rb - ra; - sample = (ra + ((dist * (Bit32s)(partialOff.pcmoffset >> 8)) >> 8)); - } else { - // Sound decimation - // The right way to do it is to use a lowpass filter on the waveform before selecting - // a point. This is too slow. The following approximates this as fast as possible - int idelta = delta >> 16; - taddr = pcmAddr + partialOff.pcmplace; - ra = synth->pcmROMData[taddr++]; - for (int ix = 0; ix < idelta - 1; ix++) { - if (taddr == pcmAddr + pcmWave->len) { - // Past end of PCM - if (pcmWave->loop) { - taddr = pcmAddr; - } else { - // Behave as if all subsequent samples were 0 - break; - } - } - ra += synth->pcmROMData[taddr++]; - } - sample = ra / idelta; - } - } else { - // Render synthesised sample - int toff = partialOff.pcmplace; - int minorplace = partialOff.pcmoffset >> 14; - Bit32s filterInput; - Bit32s filtval = getFiltEnvelope(); - - //synth->printDebug("Filtval: %d", filtval); - - if ((patchCache->waveform & 1) == 0) { - // Square waveform. Made by combining two pregenerated bandlimited - // sawtooth waveforms - Bit32u ofsA = ((toff << 2) + minorplace) % noteLookup->waveformSize[0]; - int width = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[pulsewidth], 7); - Bit32u ofsB = (ofsA + width) % noteLookup->waveformSize[0]; - Bit16s pa = noteLookup->waveforms[0][ofsA]; - Bit16s pb = noteLookup->waveforms[0][ofsB]; - filterInput = pa - pb; - // Non-bandlimited squarewave - /* - ofs = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 8); - if (toff < ofs) - sample = 1 * WGAMP; - else - sample = -1 * WGAMP; - */ - } else { - // Sawtooth. Made by combining the full cosine and half cosine according - // to how it looks on the MT-32. What it really does it takes the - // square wave and multiplies it by a full cosine - int waveoff = (toff << 2) + minorplace; - if (toff < noteLookup->sawTable[pulsewidth]) - filterInput = noteLookup->waveforms[1][waveoff % noteLookup->waveformSize[1]]; - else - filterInput = noteLookup->waveforms[2][waveoff % noteLookup->waveformSize[2]]; - // This is the correct way - // Seems slow to me (though bandlimited) -- doesn't seem to - // sound any better though - /* - //int pw = (patchCache->pulsewidth * pulsemod[filtval]) >> 8; - - Bit32u ofs = toff % (noteLookup->div2 >> 1); - - Bit32u ofs3 = toff + FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 9); - ofs3 = ofs3 % (noteLookup->div2 >> 1); - - pa = noteLookup->waveforms[0][ofs]; - pb = noteLookup->waveforms[0][ofs3]; - sample = ((pa - pb) * noteLookup->waveforms[2][toff]) / 2; - */ - } - - //Very exact filter - if (filtval > ((FILTERGRAN * 15) / 16)) - filtval = ((FILTERGRAN * 15) / 16); - sample = (Bit32s)(floorf((synth->iirFilter)((float)filterInput, &history[0], synth->tables.filtCoeff[filtval][(int)patchCache->filtEnv.resonance])) / synth->tables.resonanceFactor[patchCache->filtEnv.resonance]); - if (sample < -32768) { - synth->printDebug("Overdriven amplitude for %d: %d:=%d < -32768", patchCache->waveform, filterInput, sample); - sample = -32768; - } - else if (sample > 32767) { - synth->printDebug("Overdriven amplitude for %d: %d:=%d > 32767", patchCache->waveform, filterInput, sample); - sample = 32767; - } - } - } - - // Add calculated delta to our waveform offset - Bit32u absOff = ((partialOff.pcmplace << 16) | partialOff.pcmoffset); - absOff += delta; - partialOff.pcmplace = (Bit16u)((absOff & 0xFFFF0000) >> 16); - partialOff.pcmoffset = (Bit16u)(absOff & 0xFFFF); - - // Put volume envelope over generated sample - sample = FIXEDPOINT_SMULT(sample, ampEnvVal, 9); - sample = FIXEDPOINT_SMULT(sample, volume, 7); - envs[EnvelopeType_amp].envpos++; - envs[EnvelopeType_pitch].envpos++; - envs[EnvelopeType_filt].envpos++; - - *partialBuf++ = (Bit16s)sample; - } - // We may have deactivated and broken out of the loop before the end of the buffer, - // if so then fill the remainder with 0s. - if (++length > 0) - memset(partialBuf, 0, length * 2); - return &myBuffer[0]; -} - -void Partial::setBend(float factor) { - if (!patchCache->useBender || factor == 0.0f) { - bendShift = 4096; - return; - } - // NOTE:KG: We can't do this smoothly with lookup tables, unless we use several MB. - // FIXME:KG: Bend should be influenced by pitch key-follow too, according to docs. - float bendSemitones = factor * patchCache->benderRange; // -24 .. 24 - float mult = powf(2.0f, bendSemitones / 12.0f); - synth->printDebug("setBend(): factor=%f, benderRange=%f, semitones=%f, mult=%f\n", (double)factor, (double)patchCache->benderRange, (double)bendSemitones, (double)mult); - bendShift = (int)(mult * 4096.0f); -} - -Bit16s *Partial::mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { - if (buf1 == NULL) - return buf2; - if (buf2 == NULL) - return buf1; - - Bit16s *outBuf = buf1; -#if MT32EMU_USE_MMX >= 1 - // KG: This seems to be fine - int donelen = i386_mixBuffers(buf1, buf2, len); - len -= donelen; - buf1 += donelen; - buf2 += donelen; -#endif - while (len--) { - *buf1 = *buf1 + *buf2; - buf1++, buf2++; - } - return outBuf; -} - -Bit16s *Partial::mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { - if (buf1 == NULL) - return NULL; - if (buf2 == NULL) { - Bit16s *outBuf = buf1; - while (len--) { - if (*buf1 < -8192) - *buf1 = -8192; - else if (*buf1 > 8192) - *buf1 = 8192; - buf1++; - } - return outBuf; - } - - Bit16s *outBuf = buf1; -#if MT32EMU_USE_MMX >= 1 - // KG: This seems to be fine - int donelen = i386_mixBuffersRingMix(buf1, buf2, len); - len -= donelen; - buf1 += donelen; - buf2 += donelen; -#endif - while (len--) { - float a, b; - a = ((float)*buf1) / 8192.0f; - b = ((float)*buf2) / 8192.0f; - a = (a * b) + a; - if (a > 1.0f) - a = 1.0f; - if (a < -1.0f) - a = -1.0f; - *buf1 = (Bit16s)(a * 8192.0f); - buf1++; - buf2++; - //buf1[i] = (Bit16s)(((Bit32s)buf1[i] * (Bit32s)buf2[i]) >> 10) + buf1[i]; - } - return outBuf; -} - -Bit16s *Partial::mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { - if (buf1 == NULL) { - return NULL; - } - if (buf2 == NULL) { - return NULL; - } - - Bit16s *outBuf = buf1; -#if MT32EMU_USE_MMX >= 1 - // FIXME:KG: Not really checked as working - int donelen = i386_mixBuffersRing(buf1, buf2, len); - len -= donelen; - buf1 += donelen; - buf2 += donelen; -#endif - while (len--) { - float a, b; - a = ((float)*buf1) / 8192.0f; - b = ((float)*buf2) / 8192.0f; - a *= b; - if (a > 1.0f) - a = 1.0f; - if (a < -1.0f) - a = -1.0f; - *buf1 = (Bit16s)(a * 8192.0f); - buf1++; - buf2++; - } - return outBuf; -} - -void Partial::mixBuffersStereo(Bit16s *buf1, Bit16s *buf2, Bit16s *outBuf, int len) { - if (buf2 == NULL) { - while (len--) { - *outBuf++ = *buf1++; - *outBuf++ = 0; - } - } else if (buf1 == NULL) { - while (len--) { - *outBuf++ = 0; - *outBuf++ = *buf2++; - } - } else { - while (len--) { - *outBuf++ = *buf1++; - *outBuf++ = *buf2++; - } - } -} - -bool Partial::produceOutput(Bit16s *partialBuf, long length) { - if (!isActive() || alreadyOutputed) - return false; - if (poly == NULL) { - synth->printDebug("*** ERROR: poly is NULL at Partial::produceOutput()!"); - return false; - } - - Bit16s *pairBuf = NULL; - // Check for dependant partial - if (pair != NULL) { - if (!pair->alreadyOutputed) { - // Note: pair may have become NULL after this - pairBuf = pair->generateSamples(length); - } - } else if (useNoisePair) { - // Generate noise for pairless ring mix - pairBuf = synth->tables.noiseBuf; - } - - Bit16s *myBuf = generateSamples(length); - - if (myBuf == NULL && pairBuf == NULL) - return false; - - Bit16s *p1buf, *p2buf; - - if (structurePosition == 0 || pairBuf == NULL) { - p1buf = myBuf; - p2buf = pairBuf; - } else { - p2buf = myBuf; - p1buf = pairBuf; - } - - //synth->printDebug("mixType: %d", mixType); - - Bit16s *mixedBuf; - switch (mixType) { - case 0: - // Standard sound mix - mixedBuf = mixBuffers(p1buf, p2buf, length); - break; - - case 1: - // Ring modulation with sound mix - mixedBuf = mixBuffersRingMix(p1buf, p2buf, length); - break; - - case 2: - // Ring modulation alone - mixedBuf = mixBuffersRing(p1buf, p2buf, length); - break; - - case 3: - // Stereo mixing. One partial to one speaker channel, one to another. - // FIXME:KG: Surely we should be multiplying by the left/right volumes here? - mixBuffersStereo(p1buf, p2buf, partialBuf, length); - return true; - - default: - mixedBuf = mixBuffers(p1buf, p2buf, length); - break; - } - - if (mixedBuf == NULL) - return false; - - Bit16s leftvol, rightvol; - leftvol = patchCache->pansetptr->leftvol; - rightvol = patchCache->pansetptr->rightvol; - -#if MT32EMU_USE_MMX >= 2 - // FIXME:KG: This appears to introduce crackle - int donelen = i386_partialProductOutput(length, leftvol, rightvol, partialBuf, mixedBuf); - length -= donelen; - mixedBuf += donelen; - partialBuf += donelen * 2; -#endif - while (length--) { - *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)leftvol) >> 15); - *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)rightvol) >> 15); - mixedBuf++; - } - return true; -} - -Bit32s Partial::getFiltEnvelope() { - int reshigh; - - int cutoff, depth; - - EnvelopeStatus *tStat = &envs[EnvelopeType_filt]; - - if (tStat->decaying) { - reshigh = tStat->envbase; - reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); - if (tStat->envpos >= tStat->envsize) - reshigh = 0; - } else { - if (tStat->envstat==4) { - reshigh = patchCache->filtsustain; - if (!poly->sustain) { - startDecay(EnvelopeType_filt, reshigh); - } - } else { - if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { - if (tStat->envstat==-1) - tStat->envbase = 0; - else - tStat->envbase = patchCache->filtEnv.envlevel[tStat->envstat]; - tStat->envstat++; - tStat->envpos = 0; - if (tStat->envstat == 3) { - tStat->envsize = synth->tables.envTime[(int)patchCache->filtEnv.envtime[tStat->envstat]]; - } else { - Bit32u envTime = (int)patchCache->filtEnv.envtime[tStat->envstat]; - if (tStat->envstat > 1) { - int envDiff = abs(patchCache->filtEnv.envlevel[tStat->envstat] - patchCache->filtEnv.envlevel[tStat->envstat - 1]); - if (envTime > synth->tables.envDeltaMaxTime[envDiff]) { - envTime = synth->tables.envDeltaMaxTime[envDiff]; - } - } - - tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf]) >> 8; - } - - tStat->envsize++; - tStat->envdist = patchCache->filtEnv.envlevel[tStat->envstat] - tStat->envbase; - } - - reshigh = tStat->envbase; - reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); - - } - tStat->prevlevel = reshigh; - } - - cutoff = patchCache->filtEnv.cutoff; - - //if (patchCache->waveform==1) reshigh = (reshigh * 3) >> 2; - - depth = patchCache->filtEnv.envdepth; - - //int sensedep = (depth * 127-patchCache->filtEnv.envsense) >> 7; - depth = FIXEDPOINT_UMULT(depth, synth->tables.tvfVelfollowMult[poly->vel][(int)patchCache->filtEnv.envsense], 8); - - int bias = patchCache->tvfbias; - int dist; - - if (bias != 0) { - //FIXME:KG: Is this really based on pitch (as now), or key pressed? - //synth->printDebug("Cutoff before %d", cutoff); - if (patchCache->tvfdir == 0) { - if (noteVal < bias) { - dist = bias - noteVal; - cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8); - } - } else { - // > Bias - if (noteVal > bias) { - dist = noteVal - bias; - cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8); - } - - } - //synth->printDebug("Cutoff after %d", cutoff); - } - - depth = (depth * keyLookup->envDepthMult[patchCache->filtEnv.envdkf]) >> 8; - reshigh = (reshigh * depth) >> 7; - - Bit32s tmp; - - cutoff *= filtVal; - cutoff /= realVal; //FIXME:KG: With filter keyfollow 0, this makes no sense. What's correct? - - reshigh *= filtVal; - reshigh /= realVal; //FIXME:KG: As above for cutoff - - if (patchCache->waveform == 1) { - reshigh = (reshigh * 65) / 100; - } - - if (cutoff > 100) - cutoff = 100; - else if (cutoff < 0) - cutoff = 0; - if (reshigh > 100) - reshigh = 100; - else if (reshigh < 0) - reshigh = 0; - tmp = noteLookup->nfiltTable[cutoff][reshigh]; - //tmp *= keyfollow; - //tmp /= realfollow; - - //synth->printDebug("Cutoff %d, tmp %d, freq %d", cutoff, tmp, tmp * 256); - return tmp; -} - -bool Partial::shouldReverb() { - if (!isActive()) - return false; - return patchCache->reverb; -} - -Bit32u Partial::getAmpEnvelope() { - Bit32s tc; - - EnvelopeStatus *tStat = &envs[EnvelopeType_amp]; - - if (!play) - return 0; - - if (tStat->decaying) { - tc = tStat->envbase; - tc += (tStat->envdist * tStat->envpos) / tStat->envsize; - if (tc < 0) - tc = 0; - if ((tStat->envpos >= tStat->envsize) || (tc == 0)) { - play = false; - // Don't have to worry about prevlevel storage or anything, this partial's about to die - return 0; - } - } else { - if ((tStat->envstat == -1) || (tStat->envpos >= tStat->envsize)) { - if (tStat->envstat == -1) - tStat->envbase = 0; - else - tStat->envbase = patchCache->ampEnv.envlevel[tStat->envstat]; - tStat->envstat++; - tStat->envpos = 0; - if (tStat->envstat == 4) { - //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); - tc = patchCache->ampEnv.envlevel[3]; - if (!poly->sustain) - startDecay(EnvelopeType_amp, tc); - else - tStat->sustaining = true; - goto PastCalc; - } - Bit8u targetLevel = patchCache->ampEnv.envlevel[tStat->envstat]; - tStat->envdist = targetLevel - tStat->envbase; - Bit32u envTime = patchCache->ampEnv.envtime[tStat->envstat]; - if (targetLevel == 0) { - tStat->envsize = synth->tables.envDecayTime[envTime]; - } else { - int envLevelDelta = abs(tStat->envdist); - if (envTime > synth->tables.envDeltaMaxTime[envLevelDelta]) { - envTime = synth->tables.envDeltaMaxTime[envLevelDelta]; - } - tStat->envsize = synth->tables.envTime[envTime]; - } - - // Time keyfollow is used by all sections of the envelope (confirmed on CM-32L) - tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8); - - switch (tStat->envstat) { - case 0: - //Spot for velocity time follow - //Only used for first attack - tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, synth->tables.envTimeVelfollowMult[(int)patchCache->ampEnv.envvkf][poly->vel], 8); - //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); - break; - case 1: - case 2: - case 3: - //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); - break; - default: - synth->printDebug("Invalid TVA envelope number %d hit!", tStat->envstat); - break; - } - - tStat->envsize++; - - if (tStat->envdist != 0) { - tStat->counter = abs(tStat->envsize / tStat->envdist); - //synth->printDebug("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist); - } else { - tStat->counter = 0; - //synth->printDebug("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist); - } - } - tc = tStat->envbase; - tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); - tStat->count = tStat->counter; -PastCalc: - tc = (tc * (Bit32s)patchCache->ampEnv.level) / 100; - } - - // Prevlevel storage is bottle neck - tStat->prevlevel = tc; - - //Bias level crap stuff now - - for (int i = 0; i < 2; i++) { - if (patchCache->ampblevel[i]!=0) { - int bias = patchCache->ampbias[i]; - if (patchCache->ampdir[i]==0) { - // < Bias - if (noteVal < bias) { - int dist = bias - noteVal; - tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8); - } - } else { - // > Bias - if (noteVal > bias) { - int dist = noteVal - bias; - tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8); - } - } - } - } - if (tc < 0) { - synth->printDebug("*** ERROR: tc < 0 (%d) at getAmpEnvelope()", tc); - tc = 0; - } - return (Bit32u)tc; -} - -Bit32s Partial::getPitchEnvelope() { - EnvelopeStatus *tStat = &envs[EnvelopeType_pitch]; - - Bit32s tc; - pitchSustain = false; - if (tStat->decaying) { - if (tStat->envpos >= tStat->envsize) - tc = patchCache->pitchEnv.level[4]; - else { - tc = tStat->envbase; - tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); - } - } else { - if (tStat->envstat==3) { - tc = patchCache->pitchsustain; - if (poly->sustain) - pitchSustain = true; - else - startDecay(EnvelopeType_pitch, tc); - } else { - if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { - tStat->envstat++; - - tStat->envbase = patchCache->pitchEnv.level[tStat->envstat]; - - Bit32u envTime = patchCache->pitchEnv.time[tStat->envstat]; - int envDiff = abs(patchCache->pitchEnv.level[tStat->envstat] - patchCache->pitchEnv.level[tStat->envstat + 1]); - if (envTime > synth->tables.envDeltaMaxTime[envDiff]) { - envTime = synth->tables.envDeltaMaxTime[envDiff]; - } - - tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow]) >> 8; - - tStat->envpos = 0; - tStat->envsize++; - tStat->envdist = patchCache->pitchEnv.level[tStat->envstat + 1] - tStat->envbase; - } - tc = tStat->envbase; - tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); - } - tStat->prevlevel = tc; - } - return tc; -} - -void Partial::startDecayAll() { - startDecay(EnvelopeType_amp, envs[EnvelopeType_amp].prevlevel); - startDecay(EnvelopeType_filt, envs[EnvelopeType_filt].prevlevel); - startDecay(EnvelopeType_pitch, envs[EnvelopeType_pitch].prevlevel); - pitchSustain = false; -} - -void Partial::startDecay(EnvelopeType envnum, Bit32s startval) { - EnvelopeStatus *tStat = &envs[envnum]; - - tStat->sustaining = false; - tStat->decaying = true; - tStat->envpos = 0; - tStat->envbase = startval; - - switch (envnum) { - case EnvelopeType_amp: - tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->ampEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8); - tStat->envdist = -startval; - break; - case EnvelopeType_filt: - tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->filtEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf], 8); - tStat->envdist = -startval; - break; - case EnvelopeType_pitch: - tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->pitchEnv.time[3]], keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow], 8); - tStat->envdist = patchCache->pitchEnv.level[4] - startval; - break; - default: - break; - } - tStat->envsize++; -} diff --git a/sound/softsynth/mt32/partial.h b/sound/softsynth/mt32/partial.h deleted file mode 100644 index 93d8bcd985..0000000000 --- a/sound/softsynth/mt32/partial.h +++ /dev/null @@ -1,148 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef MT32EMU_PARTIAL_H -#define MT32EMU_PARTIAL_H - -namespace MT32Emu { - -class Synth; -struct NoteLookup; - -enum EnvelopeType { - EnvelopeType_amp = 0, - EnvelopeType_filt = 1, - EnvelopeType_pitch = 2 -}; - -struct EnvelopeStatus { - Bit32s envpos; - Bit32s envstat; - Bit32s envbase; - Bit32s envdist; - Bit32s envsize; - - bool sustaining; - bool decaying; - Bit32s prevlevel; - - Bit32s counter; - Bit32s count; -}; - -// Class definition of MT-32 partials. 32 in all. -class Partial { -private: - Synth *synth; - - int ownerPart; // -1 if unassigned - int mixType; - int structurePosition; // 0 or 1 of a structure pair - bool useNoisePair; - - Bit16s myBuffer[MAX_SAMPLE_OUTPUT]; - - // Keyfollowed note value -#if MT32EMU_ACCURATENOTES == 1 - NoteLookup noteLookupStorage; - float noteVal; -#else - int noteVal; - int fineShift; -#endif - const NoteLookup *noteLookup; // LUTs for this noteVal - const KeyLookup *keyLookup; // LUTs for the clamped (12..108) key - - // Keyfollowed filter values - int realVal; - int filtVal; - - // Only used for PCM partials - int pcmNum; - PCMWaveEntry *pcmWave; - - int pulsewidth; - - Bit32u lfoPos; - soundaddr partialOff; - - Bit32u ampEnvVal; - Bit32u pitchEnvVal; - - float history[32]; - - bool pitchSustain; - - int loopPos; - - dpoly *poly; - - int bendShift; - - Bit16s *mixBuffers(Bit16s *buf1, Bit16s *buf2, int len); - Bit16s *mixBuffersRingMix(Bit16s *buf1, Bit16s *buf2, int len); - Bit16s *mixBuffersRing(Bit16s *buf1, Bit16s *buf2, int len); - void mixBuffersStereo(Bit16s *buf1, Bit16s *buf2, Bit16s *outBuf, int len); - - Bit32s getFiltEnvelope(); - Bit32u getAmpEnvelope(); - Bit32s getPitchEnvelope(); - - void initKeyFollow(int freqNum); - -public: - const PatchCache *patchCache; - EnvelopeStatus envs[3]; - bool play; - - PatchCache cachebackup; - - Partial *pair; - bool alreadyOutputed; - Bit32u age; - - Partial(Synth *synth); - ~Partial(); - - int getOwnerPart() const; - int getKey() const; - const dpoly *getDpoly() const; - bool isActive(); - void activate(int part); - void deactivate(void); - void startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial); - void startDecay(EnvelopeType envnum, Bit32s startval); - void startDecayAll(); - void setBend(float factor); - bool shouldReverb(); - - // Returns true only if data written to buffer - // This function (unlike the one below it) returns processed stereo samples - // made from combining this single partial with its pair, if it has one. - bool produceOutput(Bit16s * partialBuf, long length); - - // This function produces mono sample output using the partial's private internal buffer - Bit16s *generateSamples(long length); -}; - -} - -#endif diff --git a/sound/softsynth/mt32/partialManager.cpp b/sound/softsynth/mt32/partialManager.cpp deleted file mode 100644 index 3d3b6302db..0000000000 --- a/sound/softsynth/mt32/partialManager.cpp +++ /dev/null @@ -1,272 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include - -#include "mt32emu.h" - -using namespace MT32Emu; - -PartialManager::PartialManager(Synth *useSynth) { - this->synth = useSynth; - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) - partialTable[i] = new Partial(synth); -} - -PartialManager::~PartialManager(void) { - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) - delete partialTable[i]; -} - -void PartialManager::getPerPartPartialUsage(int usage[9]) { - memset(usage, 0, 9 * sizeof (int)); - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (partialTable[i]->isActive()) - usage[partialTable[i]->getOwnerPart()]++; - } -} - -void PartialManager::clearAlreadyOutputed() { - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) - partialTable[i]->alreadyOutputed = false; -} - -void PartialManager::ageAll() { - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) - partialTable[i]->age++; -} - -bool PartialManager::shouldReverb(int i) { - return partialTable[i]->shouldReverb(); -} - -bool PartialManager::produceOutput(int i, Bit16s *buffer, Bit32u bufferLength) { - return partialTable[i]->produceOutput(buffer, bufferLength); -} - -void PartialManager::deactivateAll() { - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - partialTable[i]->deactivate(); - } -} - -unsigned int PartialManager::setReserve(Bit8u *rset) { - unsigned int pr = 0; - for (int x = 0; x < 9; x++) { - for (int y = 0; y < rset[x]; y++) { - partialReserveTable[pr] = x; - pr++; - } - } - return pr; -} - -Partial *PartialManager::allocPartial(int partNum) { - Partial *outPartial = NULL; - - // Use the first inactive partial reserved for the specified part (if there are any) - // Otherwise, use the last inactive partial, if any - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (!partialTable[i]->isActive()) { - outPartial = partialTable[i]; - if (partialReserveTable[i] == partNum) - break; - } - } - if (outPartial != NULL) { - outPartial->activate(partNum); - outPartial->age = 0; - } - return outPartial; -} - -unsigned int PartialManager::getFreePartialCount(void) { - int count = 0; - memset(partialPart, 0, sizeof(partialPart)); - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (!partialTable[i]->isActive()) - count++; - else - partialPart[partialTable[i]->getOwnerPart()]++; - } - return count; -} - -/* -bool PartialManager::freePartials(unsigned int needed, int partNum) { - int i; - int myPartPrior = (int)mt32ram.system.reserveSettings[partNum]; - if (myPartPrior 0) { - int selectPart = -1; - //Find the worst offender with more partials than allocated and kill them - most = -1; - mostPart = -1; - int diff; - - for (i=0;i<9;i++) { - diff = partialPart[i] - (int)mt32ram.system.reserveSettings[i]; - - if (diff>0) { - if (diff>most) { - most = diff; - mostPart = i; - } - } - } - selectPart = mostPart; - if (selectPart == -1) { - // All parts are within the allocated limits, you suck - // Look for first partial not of this part that's decaying perhaps? - return false; - } - bool found; - int oldest; - int oldnum; - while (partialPart[selectPart] > (int)mt32ram.system.reserveSettings[selectPart]) { - oldest = -1; - oldnum = -1; - found = false; - for (i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (partialTable[i]->isActive) { - if (partialTable[i]->ownerPart == selectPart) { - found = true; - if (partialTable[i]->age > oldest) { - oldest = partialTable[i]->age; - oldnum = i; - } - } - } - } - if (!found) break; - partialTable[oldnum]->deactivate(); - --partialPart[selectPart]; - --needed; - } - - } - return true; - - } else { - //This part has reached its max, must kill off its own - bool found; - int oldest; - int oldnum; - while (needed > 0) { - oldest = -1; - oldnum = -1; - found = false; - for (i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (partialTable[i]->isActive) { - if (partialTable[i]->ownerPart == partNum) { - found = true; - if (partialTable[i]->age > oldest) { - oldest = partialTable[i]->age; - oldnum = i; - } - } - } - } - if (!found) break; - partialTable[oldnum]->deactivate(); - --needed; - } - // Couldn't free enough partials, sorry - if (needed>0) return false; - return true; - } - -} -*/ -bool PartialManager::freePartials(unsigned int needed, int partNum) { - if (needed == 0) { - return true; - } - // Reclaim partials reserved for this part - // Kill those that are already decaying first - /* - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (partialReserveTable[i] == partNum) { - if (partialTable[i]->ownerPart != partNum) { - if (partialTable[i]->partCache->envs[AMPENV].decaying) { - partialTable[i]->isActive = false; - --needed; - if (needed == 0) - return true; - } - } - } - }*/ - // Then kill those with the lowest part priority -- oldest at the moment - while (needed > 0) { - Bit32u prior = 0; - int priornum = -1; - - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (partialReserveTable[i] == partNum && partialTable[i]->isActive() && partialTable[i]->getOwnerPart() != partNum) { - /* - if (mt32ram.system.reserveSettings[partialTable[i]->ownerPart] < prior) { - prior = mt32ram.system.reserveSettings[partialTable[i]->ownerPart]; - priornum = i; - }*/ - if (partialTable[i]->age >= prior) { - prior = partialTable[i]->age; - priornum = i; - } - } - } - if (priornum != -1) { - partialTable[priornum]->deactivate(); - --needed; - } else { - break; - } - } - - // Kill off the oldest partials within this part - while (needed > 0) { - Bit32u oldest = 0; - int oldlist = -1; - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (partialTable[i]->getOwnerPart() == partNum && partialTable[i]->isActive()) { - if (partialTable[i]->age >= oldest) { - oldest = partialTable[i]->age; - oldlist = i; - } - } - } - if (oldlist != -1) { - partialTable[oldlist]->deactivate(); - --needed; - } else { - break; - } - } - return needed == 0; -} - -const Partial *PartialManager::getPartial(unsigned int partialNum) const { - if (partialNum > MT32EMU_MAX_PARTIALS - 1) - return NULL; - return partialTable[partialNum]; -} diff --git a/sound/softsynth/mt32/partialManager.h b/sound/softsynth/mt32/partialManager.h deleted file mode 100644 index b10f93ff02..0000000000 --- a/sound/softsynth/mt32/partialManager.h +++ /dev/null @@ -1,56 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef MT32EMU_PARTIALMANAGER_H -#define MT32EMU_PARTIALMANAGER_H - -namespace MT32Emu { - -class Synth; - -class PartialManager { -private: - Synth *synth; // Only used for sending debug output - - Partial *partialTable[MT32EMU_MAX_PARTIALS]; - Bit32s partialReserveTable[MT32EMU_MAX_PARTIALS]; - Bit32s partialPart[9]; // The count of partials played per part - -public: - - PartialManager(Synth *synth); - ~PartialManager(); - Partial *allocPartial(int partNum); - unsigned int getFreePartialCount(void); - bool freePartials(unsigned int needed, int partNum); - unsigned int setReserve(Bit8u *rset); - void deactivateAll(); - void ageAll(); - bool produceOutput(int i, Bit16s *buffer, Bit32u bufferLength); - bool shouldReverb(int i); - void clearAlreadyOutputed(); - void getPerPartPartialUsage(int usage[9]); - const Partial *getPartial(unsigned int partialNum) const; -}; - -} - -#endif diff --git a/sound/softsynth/mt32/structures.h b/sound/softsynth/mt32/structures.h deleted file mode 100644 index ef58c1d20f..0000000000 --- a/sound/softsynth/mt32/structures.h +++ /dev/null @@ -1,284 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef MT32EMU_STRUCTURES_H -#define MT32EMU_STRUCTURES_H - -namespace MT32Emu { - -const unsigned int MAX_SAMPLE_OUTPUT = 4096; - -// MT32EMU_MEMADDR() converts from sysex-padded, MT32EMU_SYSEXMEMADDR converts to it -// Roland provides documentation using the sysex-padded addresses, so we tend to use that in code and output -#define MT32EMU_MEMADDR(x) ((((x) & 0x7f0000) >> 2) | (((x) & 0x7f00) >> 1) | ((x) & 0x7f)) -#define MT32EMU_SYSEXMEMADDR(x) ((((x) & 0x1FC000) << 2) | (((x) & 0x3F80) << 1) | ((x) & 0x7f)) - -#ifdef _MSC_VER -#define MT32EMU_ALIGN_PACKED __declspec(align(1)) -typedef unsigned __int64 Bit64u; -typedef signed __int64 Bit64s; -#else -#define MT32EMU_ALIGN_PACKED __attribute__((packed)) -typedef unsigned long long Bit64u; -typedef signed long long Bit64s; -#endif - -typedef unsigned int Bit32u; -typedef signed int Bit32s; -typedef unsigned short int Bit16u; -typedef signed short int Bit16s; -typedef unsigned char Bit8u; -typedef signed char Bit8s; - -// The following structures represent the MT-32's memory -// Since sysex allows this memory to be written to in blocks of bytes, -// we keep this packed so that we can copy data into the various -// banks directly -#if defined(_MSC_VER) || defined (__MINGW32__) -#pragma pack(push, 1) -#else -#pragma pack(1) -#endif - -struct TimbreParam { - struct commonParam { - char name[10]; - Bit8u pstruct12; // 1&2 0-12 (1-13) - Bit8u pstruct34; // #3&4 0-12 (1-13) - Bit8u pmute; // 0-15 (0000-1111) - Bit8u nosustain; // 0-1(Normal, No sustain) - } MT32EMU_ALIGN_PACKED common; - - struct partialParam { - struct wgParam { - Bit8u coarse; // 0-96 (C1,C#1-C9) - Bit8u fine; // 0-100 (-50 to +50 (cents?)) - Bit8u keyfollow; // 0-16 (-1,-1/2,0,1,1/8,1/4,3/8,1/2,5/8,3/4,7/8,1,5/4,3/2,2.s1,s2) - Bit8u bender; // 0,1 (ON/OFF) - Bit8u waveform; // MT-32: 0-1 (SQU/SAW); LAPC-I: WG WAVEFORM/PCM BANK 0 - 3 (SQU/1, SAW/1, SQU/2, SAW/2) - Bit8u pcmwave; // 0-127 (1-128) - Bit8u pulsewid; // 0-100 - Bit8u pwvelo; // 0-14 (-7 - +7) - } MT32EMU_ALIGN_PACKED wg; - - struct envParam { - Bit8u depth; // 0-10 - Bit8u sensitivity; // 1-100 - Bit8u timekeyfollow; // 0-4 - Bit8u time[4]; // 1-100 - Bit8u level[5]; // 1-100 (-50 - +50) - } MT32EMU_ALIGN_PACKED env; - - struct lfoParam { - Bit8u rate; // 0-100 - Bit8u depth; // 0-100 - Bit8u modsense; // 0-100 - } MT32EMU_ALIGN_PACKED lfo; - - struct tvfParam { - Bit8u cutoff; // 0-100 - Bit8u resonance; // 0-30 - Bit8u keyfollow; // 0-16 (-1,-1/2,1/4,0,1,1/8,1/4,3/8,1/2,5/8,3/2,7/8,1,5/4,3/2,2,s1,s2) - Bit8u biaspoint; // 0-127 (<1A-<7C >1A-7C) - Bit8u biaslevel; // 0-14 (-7 - +7) - Bit8u envdepth; // 0-100 - Bit8u envsense; // 0-100 - Bit8u envdkf; // DEPTH KEY FOLL0W 0-4 - Bit8u envtkf; // TIME KEY FOLLOW 0-4 - Bit8u envtime[5]; // 1-100 - Bit8u envlevel[4]; // 1-100 - } MT32EMU_ALIGN_PACKED tvf; - - struct tvaParam { - Bit8u level; // 0-100 - Bit8u velosens; // 0-100 - Bit8u biaspoint1; // 0-127 (<1A-<7C >1A-7C) - Bit8u biaslevel1; // 0-12 (-12 - 0) - Bit8u biaspoint2; // 0-127 (<1A-<7C >1A-7C) - Bit8u biaslevel2; // 0-12 (-12 - 0) - Bit8u envtkf; // TIME KEY FOLLOW 0-4 - Bit8u envvkf; // VELOS KEY FOLL0W 0-4 - Bit8u envtime[5]; // 1-100 - Bit8u envlevel[4]; // 1-100 - } MT32EMU_ALIGN_PACKED tva; - } MT32EMU_ALIGN_PACKED partial[4]; -} MT32EMU_ALIGN_PACKED; - -struct PatchParam { - Bit8u timbreGroup; // TIMBRE GROUP 0-3 (group A, group B, Memory, Rhythm) - Bit8u timbreNum; // TIMBRE NUMBER 0-63 - Bit8u keyShift; // KEY SHIFT 0-48 (-24 - +24 semitones) - Bit8u fineTune; // FINE TUNE 0-100 (-50 - +50 cents) - Bit8u benderRange; // BENDER RANGE 0-24 - Bit8u assignMode; // ASSIGN MODE 0-3 (POLY1, POLY2, POLY3, POLY4) - Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) - Bit8u dummy; // (DUMMY) -} MT32EMU_ALIGN_PACKED; - -struct MemParams { - // NOTE: The MT-32 documentation only specifies PatchTemp areas for parts 1-8. - // The LAPC-I documentation specified an additional area for rhythm at the end, - // where all parameters but fine tune, assign mode and output level are ignored - struct PatchTemp { - PatchParam patch; - Bit8u outlevel; // OUTPUT LEVEL 0-100 - Bit8u panpot; // PANPOT 0-14 (R-L) - Bit8u dummyv[6]; - } MT32EMU_ALIGN_PACKED; - - PatchTemp patchSettings[9]; - - struct RhythmTemp { - Bit8u timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF) - Bit8u outlevel; // OUTPUT LEVEL 0-100 - Bit8u panpot; // PANPOT 0-14 (R-L) - Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) - } MT32EMU_ALIGN_PACKED; - - RhythmTemp rhythmSettings[85]; - - TimbreParam timbreSettings[8]; - - PatchParam patches[128]; - - // NOTE: There are only 30 timbres in the "rhythm" bank for MT-32; the additional 34 are for LAPC-I and above - struct PaddedTimbre { - TimbreParam timbre; - Bit8u padding[10]; - } MT32EMU_ALIGN_PACKED; - - PaddedTimbre timbres[64 + 64 + 64 + 64]; // Group A, Group B, Memory, Rhythm - - struct SystemArea { - Bit8u masterTune; // MASTER TUNE 0-127 432.1-457.6Hz - Bit8u reverbMode; // REVERB MODE 0-3 (room, hall, plate, tap delay) - Bit8u reverbTime; // REVERB TIME 0-7 (1-8) - Bit8u reverbLevel; // REVERB LEVEL 0-7 (1-8) - Bit8u reserveSettings[9]; // PARTIAL RESERVE (PART 1) 0-32 - Bit8u chanAssign[9]; // MIDI CHANNEL (PART1) 0-16 (1-16,OFF) - Bit8u masterVol; // MASTER VOLUME 0-100 - } MT32EMU_ALIGN_PACKED; - - SystemArea system; -}; - -#if defined(_MSC_VER) || defined (__MINGW32__) -#pragma pack(pop) -#else -#pragma pack() -#endif - -struct PCMWaveEntry { - Bit32u addr; - Bit32u len; - double tune; - bool loop; -}; - -struct soundaddr { - Bit16u pcmplace; - Bit16u pcmoffset; -}; - -struct StereoVolume { - Bit16s leftvol; - Bit16s rightvol; -}; - -// This is basically a per-partial, pre-processed combination of timbre and patch/rhythm settings -struct PatchCache { - bool playPartial; - bool PCMPartial; - int pcm; - char waveform; - int pulsewidth; - int pwsens; - - float pitch; - - int lfodepth; - int lforate; - Bit32u lfoperiod; - int modsense; - - float pitchKeyfollow; - - int filtkeyfollow; - - int tvfbias; - int tvfblevel; - int tvfdir; - - int ampbias[2]; - int ampblevel[2]; - int ampdir[2]; - - int ampdepth; - int amplevel; - - bool useBender; - float benderRange; // 0.0, 1.0, .., 24.0 (semitones) - - TimbreParam::partialParam::envParam pitchEnv; - TimbreParam::partialParam::tvaParam ampEnv; - TimbreParam::partialParam::tvfParam filtEnv; - - Bit32s pitchsustain; - Bit32s filtsustain; - - Bit32u structureMix; - int structurePosition; - int structurePair; - - // The following fields are actually common to all partials in the timbre - bool dirty; - Bit32u partialCount; - bool sustain; - float pitchShift; - bool reverb; - const StereoVolume *pansetptr; -}; - -class Partial; // Forward reference for class defined in partial.h - -struct dpoly { - bool isPlaying; - - unsigned int key; - int freqnum; - int vel; - - bool isDecay; - - const Bit32u *volumeptr; - - Partial *partials[4]; - - bool pedalhold; // This marks keys that have been released on the keyboard, but are being held by the pedal - bool sustain; - - bool isActive() const; - Bit32u getAge() const; -}; - -} - -#endif diff --git a/sound/softsynth/mt32/synth.cpp b/sound/softsynth/mt32/synth.cpp deleted file mode 100644 index 16460795a5..0000000000 --- a/sound/softsynth/mt32/synth.cpp +++ /dev/null @@ -1,1198 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include -#include -#include - -#include "mt32emu.h" - -#include "common/str.h" - -#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__) -// Older versions of Mac OS X didn't supply a powf function, so using it -// will cause a binary incompatibility when trying to run a binary built -// on a newer OS X release on an olderr one. And Solaris 8 doesn't provide -// powf, floorf, fabsf etc. at all. -// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in -// libstdc++. math/stubs.o should be empty, but it comes with a symbol for -// powf, resulting in a linker error because of multiple definitions. -// Hence we re-define them here. The only potential drawback is that it -// might be a little bit slower this way. -#define powf(x,y) ((float)pow(x,y)) -#define floorf(x) ((float)floor(x)) -#define fabsf(x) ((float)fabs(x)) -#endif - -namespace MT32Emu { - -const int MAX_SYSEX_SIZE = 512; - -const ControlROMMap ControlROMMaps[5] = { - // ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrB tmbrBO, tmbrR trC rhythm rhyC rsrv panpot prog - {0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57D0, 0x57E2}, // MT-32 revision 0 - {0x4014, 22, "\000 ver1.06 31 Aug, 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x7414, 85, 0x57D9, 0x57E2, 0x57F4}, // MT-32 revision 0 - {0x4010, 22, "\000 ver1.07 10 Oct, 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57BA, 0x57CC}, // MT-32 revision 1 - {0x4010, 22, "\000verX.XX 30 Sep, 88 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x741C, 85, 0x57E5, 0x57EE, 0x5800}, // MT-32 Blue Ridge mod - {0x2205, 22, "\000CM32/LAPC1.02 891205", 0x8100, 256, 0x8000, 0x8000, 0x8080, 0x8000, 0x8500, 64, 0x8580, 85, 0x4F93, 0x4F9C, 0x4FAE} // CM-32L - // (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp) -}; - -float iir_filter_normal(float input, float *hist1_ptr, float *coef_ptr) { - float *hist2_ptr; - float output,new_hist; - - hist2_ptr = hist1_ptr + 1; // next history - - // 1st number of coefficients array is overall input scale factor, or filter gain - output = input * (*coef_ptr++); - - output = output - *hist1_ptr * (*coef_ptr++); - new_hist = output - *hist2_ptr * (*coef_ptr++); // poles - - output = new_hist + *hist1_ptr * (*coef_ptr++); - output = output + *hist2_ptr * (*coef_ptr++); // zeros - - *hist2_ptr++ = *hist1_ptr; - *hist1_ptr++ = new_hist; - hist1_ptr++; - hist2_ptr++; - - // i = 1 - output = output - *hist1_ptr * (*coef_ptr++); - new_hist = output - *hist2_ptr * (*coef_ptr++); // poles - - output = new_hist + *hist1_ptr * (*coef_ptr++); - output = output + *hist2_ptr * (*coef_ptr++); // zeros - - *hist2_ptr++ = *hist1_ptr; - *hist1_ptr++ = new_hist; - - return(output); -} - -Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) { - for (unsigned int i = 0; i < len; i++) { - checksum = checksum + data[i]; - } - checksum = checksum & 0x7f; - if (checksum) - checksum = 0x80 - checksum; - return checksum; -} - -Synth::Synth() { - isOpen = false; - reverbModel = NULL; - partialManager = NULL; - memset(parts, 0, sizeof(parts)); -} - -Synth::~Synth() { - close(); // Make sure we're closed and everything is freed -} - -int Synth::report(ReportType type, const void *data) { - if (myProp.report != NULL) { - return myProp.report(myProp.userData, type, data); - } - return 0; -} - -void Synth::printDebug(const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - if (myProp.printDebug != NULL) { - myProp.printDebug(myProp.userData, fmt, ap); - } else { - vprintf(fmt, ap); - printf("\n"); - } - va_end(ap); -} - -void Synth::initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel) { - // FIXME:KG: I don't think it's necessary to recreate the reverbModel... Just set the parameters - delete reverbModel; - reverbModel = new revmodel(); - - switch (newRevMode) { - case 0: - reverbModel->setroomsize(.1f); - reverbModel->setdamp(.75f); - break; - case 1: - reverbModel->setroomsize(.5f); - reverbModel->setdamp(.5f); - break; - case 2: - reverbModel->setroomsize(.5f); - reverbModel->setdamp(.1f); - break; - case 3: - reverbModel->setroomsize(1.0f); - reverbModel->setdamp(.75f); - break; - default: - reverbModel->setroomsize(.1f); - reverbModel->setdamp(.5f); - break; - } - reverbModel->setdry(1); - reverbModel->setwet((float)newRevLevel / 8.0f); - reverbModel->setwidth((float)newRevTime / 8.0f); -} - -File *Synth::openFile(const char *filename, File::OpenMode mode) { - // It should never happen that openFile is NULL in our use case. - // Just to cover the case where something is horrible wrong we - // use an assert here. - assert(myProp.openFile != NULL); - return myProp.openFile(myProp.userData, filename, mode); -} - -void Synth::closeFile(File *file) { - if (myProp.closeFile != NULL) { - myProp.closeFile(myProp.userData, file); - } else { - file->close(); - delete file; - } -} - -bool Synth::loadPreset(File *file) { - bool inSys = false; - Bit8u sysexBuf[MAX_SYSEX_SIZE]; - Bit16u syslen = 0; - bool rc = true; - for (;;) { - Bit8u c; - if (!file->readBit8u(&c)) { - if (!file->isEOF()) { - rc = false; - } - break; - } - sysexBuf[syslen] = c; - if (inSys) { - syslen++; - if (c == 0xF7) { - playSysex(&sysexBuf[0], syslen); - inSys = false; - syslen = 0; - } else if (syslen == MAX_SYSEX_SIZE) { - printDebug("MAX_SYSEX_SIZE (%d) exceeded while processing preset, ignoring message", MAX_SYSEX_SIZE); - inSys = false; - syslen = 0; - } - } else if (c == 0xF0) { - syslen++; - inSys = true; - } - } - return rc; -} - -bool Synth::loadControlROM(const char *filename) { - File *file = openFile(filename, File::OpenMode_read); // ROM File - if (file == NULL) { - return false; - } - bool rc = (file->read(controlROMData, CONTROL_ROM_SIZE) == CONTROL_ROM_SIZE); - - closeFile(file); - if (!rc) - return rc; - - // Control ROM successfully loaded, now check whether it's a known type - controlROMMap = NULL; - for (unsigned int i = 0; i < sizeof (ControlROMMaps) / sizeof (ControlROMMaps[0]); i++) { - if (memcmp(&controlROMData[ControlROMMaps[i].idPos], ControlROMMaps[i].idBytes, ControlROMMaps[i].idLen) == 0) { - controlROMMap = &ControlROMMaps[i]; - return true; - } - } - return false; -} - -bool Synth::loadPCMROM(const char *filename) { - File *file = openFile(filename, File::OpenMode_read); // ROM File - if (file == NULL) { - return false; - } - bool rc = true; - int i; - for (i = 0; i < pcmROMSize; i++) { - Bit8u s; - if (!file->readBit8u(&s)) { - if (!file->isEOF()) { - rc = false; - } - break; - } - Bit8u c; - if (!file->readBit8u(&c)) { - if (!file->isEOF()) { - rc = false; - } else { - printDebug("PCM ROM file has an odd number of bytes! Ignoring last"); - } - break; - } - - short e; - int bit; - int u; - int order[16] = {0, 9, 1 ,2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8}; - - e = 0; - for (u = 0; u < 15; u++) { - if (order[u] < 8) - bit = (s >> (7 - order[u])) & 0x1; - else - bit = (c >> (7 - (order[u] - 8))) & 0x1; - e = e | (short)(bit << (15 - u)); - } - - /* - //Bit16s e = ( ((s & 0x7f) << 4) | ((c & 0x40) << 6) | ((s & 0x80) << 6) | ((c & 0x3f))) << 2; - if (e<0) - e = -32767 - e; - int ut = abs(e); - int dif = 0x7fff - ut; - x = exp(((float)((float)0x8000-(float)dif) / (float)0x1000)); - e = (int)((float)e * (x/3200)); - */ - - // File is companded (dB?), convert to linear PCM - // MINDB = -96 - // MAXDB = -15 - float testval; - testval = (float)((~e) & 0x7fff); - testval = -(testval / 400.00f); - //testval = -(testval / 341.32291666666666666666666666667); - float vol = powf(8, testval / 20) * 32767.0f; - - if (e > 0) - vol = -vol; - - pcmROMData[i] = (Bit16s)vol; - } - if (i != pcmROMSize) { - printDebug("PCM ROM file is too short (expected %d, got %d)", pcmROMSize, i); - rc = false; - } - closeFile(file); - return rc; -} - -bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) { - ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress]; - for (int i = 0; i < count; i++) { - int rAddr = tps[i].pos * 0x800; - int rLenExp = (tps[i].len & 0x70) >> 4; - int rLen = 0x800 << rLenExp; - bool rLoop = (tps[i].len & 0x80) != 0; - //Bit8u rFlag = tps[i].len & 0x0F; - Bit16u rTuneOffset = (tps[i].pitchMSB << 8) | tps[i].pitchLSB; - // The number below is confirmed to a reasonable degree of accuracy on CM-32L - double STANDARDFREQ = 442.0; - float rTune = (float)(STANDARDFREQ * pow(2.0, (0x5000 - rTuneOffset) / 4056.0 - 9.0 / 12.0)); - //printDebug("%f,%d,%d", (double)pTune, tps[i].pitchCoarse, tps[i].pitchFine); - if (rAddr + rLen > pcmROMSize) { - printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen); - return false; - } - pcmWaves[i].addr = rAddr; - pcmWaves[i].len = rLen; - pcmWaves[i].loop = rLoop; - pcmWaves[i].tune = rTune; - } - return false; -} - -bool Synth::initRhythmTimbre(int timbreNum, const Bit8u *mem, unsigned int memLen) { - if (memLen < sizeof(TimbreParam::commonParam)) { - return false; - } - TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; - memcpy(&timbre->common, mem, 14); - unsigned int memPos = 14; - char drumname[11]; - memset(drumname, 0, 11); - memcpy(drumname, timbre->common.name, 10); - for (int t = 0; t < 4; t++) { - if (((timbre->common.pmute >> t) & 0x1) == 0x1) { - if (memPos + 58 >= memLen) { - return false; - } - memcpy(&timbre->partial[t], mem + memPos, 58); - memPos += 58; - } - } - return true; -} - -bool Synth::initRhythmTimbres(Bit16u mapAddress, Bit16u count) { - const Bit8u *drumMap = &controlROMData[mapAddress]; - int timbreNum = 192; - for (Bit16u i = 0; i < count * 2; i += 2) { - Bit16u address = (drumMap[i + 1] << 8) | drumMap[i]; - /* - // This check is nonsensical when the control ROM is the full 64KB addressable by 16-bit absolute pointers (which it is) - if (address >= CONTROL_ROM_SIZE) { - printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address); - return false; - } - */ - if (!initRhythmTimbre(timbreNum++, &controlROMData[address], CONTROL_ROM_SIZE - address)) { - printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre 0x%04x", i, address); - return false; - } - } - return true; -} - -bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre) { - for (Bit16u i = mapAddress; i < mapAddress + 0x80; i += 2) { - Bit16u address = (controlROMData[i + 1] << 8) | controlROMData[i]; - if (address + sizeof(TimbreParam) > CONTROL_ROM_SIZE) { - printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address); - return false; - } - address = address + offset; - TimbreParam *timbre = &mt32ram.timbres[startTimbre++].timbre; - memcpy(timbre, &controlROMData[address], sizeof(TimbreParam)); - } - return true; -} - -bool Synth::open(SynthProperties &useProp) { - if (isOpen) - return false; - - myProp = useProp; - if (useProp.baseDir != NULL) { - myProp.baseDir = new char[strlen(useProp.baseDir) + 1]; - strcpy(myProp.baseDir, useProp.baseDir); - } - - // This is to help detect bugs - memset(&mt32ram, '?', sizeof(mt32ram)); - - printDebug("Loading Control ROM"); - if (!loadControlROM("CM32L_CONTROL.ROM")) { - if (!loadControlROM("MT32_CONTROL.ROM")) { - printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM"); - report(ReportType_errorControlROM, NULL); - return false; - } - } - - // 512KB PCM ROM for MT-32, etc. - // 1MB PCM ROM for CM-32L, LAPC-I, CM-64, CM-500 - // Note that the size below is given in samples (16-bit), not bytes - pcmROMSize = controlROMMap->pcmCount == 256 ? 512 * 1024 : 256 * 1024; - pcmROMData = new Bit16s[pcmROMSize]; - - printDebug("Loading PCM ROM"); - if (!loadPCMROM("CM32L_PCM.ROM")) { - if (!loadPCMROM("MT32_PCM.ROM")) { - printDebug("Init Error - Missing MT32_PCM.ROM"); - report(ReportType_errorPCMROM, NULL); - return false; - } - } - - printDebug("Initialising Timbre Bank A"); - if (!initTimbres(controlROMMap->timbreAMap, controlROMMap->timbreAOffset, 0)) { - return false; - } - - printDebug("Initialising Timbre Bank B"); - if (!initTimbres(controlROMMap->timbreBMap, controlROMMap->timbreBOffset, 64)) { - return false; - } - - printDebug("Initialising Timbre Bank R"); - if (!initRhythmTimbres(controlROMMap->timbreRMap, controlROMMap->timbreRCount)) { - return false; - } - - printDebug("Initialising Timbre Bank M"); - // CM-64 seems to initialise all bytes in this bank to 0. - memset(&mt32ram.timbres[128], 0, sizeof (mt32ram.timbres[128]) * 64); - - partialManager = new PartialManager(this); - - pcmWaves = new PCMWaveEntry[controlROMMap->pcmCount]; - - printDebug("Initialising PCM List"); - initPCMList(controlROMMap->pcmTable, controlROMMap->pcmCount); - - printDebug("Initialising Rhythm Temp"); - memcpy(mt32ram.rhythmSettings, &controlROMData[controlROMMap->rhythmSettings], controlROMMap->rhythmSettingsCount * 4); - - printDebug("Initialising Patches"); - for (Bit8u i = 0; i < 128; i++) { - PatchParam *patch = &mt32ram.patches[i]; - patch->timbreGroup = i / 64; - patch->timbreNum = i % 64; - patch->keyShift = 24; - patch->fineTune = 50; - patch->benderRange = 12; - patch->assignMode = 0; - patch->reverbSwitch = 1; - patch->dummy = 0; - } - - printDebug("Initialising System"); - // The MT-32 manual claims that "Standard pitch" is 442Hz. - mt32ram.system.masterTune = 0x40; // Confirmed on CM-64 as 0x4A, but SCUMM games use 0x40 and we don't want to initialise twice - mt32ram.system.reverbMode = 0; // Confirmed - mt32ram.system.reverbTime = 5; // Confirmed - mt32ram.system.reverbLevel = 3; // Confirmed - memcpy(mt32ram.system.reserveSettings, &controlROMData[controlROMMap->reserveSettings], 9); // Confirmed - for (Bit8u i = 0; i < 9; i++) { - // This is the default: {1, 2, 3, 4, 5, 6, 7, 8, 9} - // An alternative configuration can be selected by holding "Master Volume" - // and pressing "PART button 1" on the real MT-32's frontpanel. - // The channel assignment is then {0, 1, 2, 3, 4, 5, 6, 7, 9} - mt32ram.system.chanAssign[i] = i + 1; - } - mt32ram.system.masterVol = 100; // Confirmed - if (!refreshSystem()) - return false; - - for (int i = 0; i < 8; i++) { - mt32ram.patchSettings[i].outlevel = 80; - mt32ram.patchSettings[i].panpot = controlROMData[controlROMMap->panSettings + i]; - memset(mt32ram.patchSettings[i].dummyv, 0, sizeof(mt32ram.patchSettings[i].dummyv)); - parts[i] = new Part(this, i); - parts[i]->setProgram(controlROMData[controlROMMap->programSettings + i]); - } - parts[8] = new RhythmPart(this, 8); - - // For resetting mt32 mid-execution - mt32default = mt32ram; - - iirFilter = &iir_filter_normal; - -#ifdef MT32EMU_HAVE_X86 - bool availableSSE = DetectSIMD(); - bool available3DNow = Detect3DNow(); - - if (availableSSE) - report(ReportType_availableSSE, NULL); - if (available3DNow) - report(ReportType_available3DNow, NULL); - - if (available3DNow) { - printDebug("Detected and using SIMD (AMD 3DNow) extensions"); - iirFilter = &iir_filter_3dnow; - report(ReportType_using3DNow, NULL); - } else if (availableSSE) { - printDebug("Detected and using SIMD (Intel SSE) extensions"); - iirFilter = &iir_filter_sse; - report(ReportType_usingSSE, NULL); - } -#endif - - isOpen = true; - isEnabled = false; - - printDebug("*** Initialisation complete ***"); - return true; -} - -void Synth::close(void) { - if (!isOpen) - return; - - tables.freeNotes(); - if (partialManager != NULL) { - delete partialManager; - partialManager = NULL; - } - - if (reverbModel != NULL) { - delete reverbModel; - reverbModel = NULL; - } - - for (int i = 0; i < 9; i++) { - if (parts[i] != NULL) { - delete parts[i]; - parts[i] = NULL; - } - } - if (myProp.baseDir != NULL) { - delete myProp.baseDir; - myProp.baseDir = NULL; - } - - delete[] pcmWaves; - delete[] pcmROMData; - isOpen = false; -} - -void Synth::playMsg(Bit32u msg) { - // FIXME: Implement active sensing - unsigned char code = (unsigned char)((msg & 0x0000F0) >> 4); - unsigned char chan = (unsigned char) (msg & 0x00000F); - unsigned char note = (unsigned char)((msg & 0x00FF00) >> 8); - unsigned char velocity = (unsigned char)((msg & 0xFF0000) >> 16); - isEnabled = true; - - //printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note); - - signed char part = chantable[chan]; - if (part < 0 || part > 8) { - printDebug("Play msg on unreg chan %d (%d): code=0x%01x, vel=%d", chan, part, code, velocity); - return; - } - playMsgOnPart(part, code, note, velocity); -} - -void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity) { - Bit32u bend; - - //printDebug("Synth::playMsg(0x%02x)",msg); - switch (code) { - case 0x8: - //printDebug("Note OFF - Part %d", part); - // The MT-32 ignores velocity for note off - parts[part]->stopNote(note); - break; - case 0x9: - //printDebug("Note ON - Part %d, Note %d Vel %d", part, note, velocity); - if (velocity == 0) { - // MIDI defines note-on with velocity 0 as being the same as note-off with velocity 40 - parts[part]->stopNote(note); - } else { - parts[part]->playNote(note, velocity); - } - break; - case 0xB: // Control change - switch (note) { - case 0x01: // Modulation - //printDebug("Modulation: %d", velocity); - parts[part]->setModulation(velocity); - break; - case 0x07: // Set volume - //printDebug("Volume set: %d", velocity); - parts[part]->setVolume(velocity); - break; - case 0x0A: // Pan - //printDebug("Pan set: %d", velocity); - parts[part]->setPan(velocity); - break; - case 0x0B: - //printDebug("Expression set: %d", velocity); - parts[part]->setExpression(velocity); - break; - case 0x40: // Hold (sustain) pedal - //printDebug("Hold pedal set: %d", velocity); - parts[part]->setHoldPedal(velocity>=64); - break; - - case 0x79: // Reset all controllers - //printDebug("Reset all controllers"); - //FIXME: Check for accuracy against real thing - parts[part]->setVolume(100); - parts[part]->setExpression(127); - parts[part]->setPan(64); - parts[part]->setBend(0x2000); - parts[part]->setHoldPedal(false); - break; - - case 0x7B: // All notes off - //printDebug("All notes off"); - parts[part]->allNotesOff(); - break; - - default: - printDebug("Unknown MIDI Control code: 0x%02x - vel 0x%02x", note, velocity); - break; - } - - break; - case 0xC: // Program change - //printDebug("Program change %01x", note); - parts[part]->setProgram(note); - break; - case 0xE: // Pitch bender - bend = (velocity << 7) | (note); - //printDebug("Pitch bender %02x", bend); - parts[part]->setBend(bend); - break; - default: - printDebug("Unknown Midi code: 0x%01x - %02x - %02x", code, note, velocity); - break; - } - - //midiOutShortMsg(m_out, msg); -} - -void Synth::playSysex(const Bit8u *sysex, Bit32u len) { - if (len < 2) { - printDebug("playSysex: Message is too short for sysex (%d bytes)", len); - } - if (sysex[0] != 0xF0) { - printDebug("playSysex: Message lacks start-of-sysex (0xF0)"); - return; - } - // Due to some programs (e.g. Java) sending buffers with junk at the end, we have to go through and find the end marker rather than relying on len. - Bit32u endPos; - for (endPos = 1; endPos < len; endPos++) - { - if (sysex[endPos] == 0xF7) - break; - } - if (endPos == len) { - printDebug("playSysex: Message lacks end-of-sysex (0xf7)"); - return; - } - playSysexWithoutFraming(sysex + 1, endPos - 1); -} - -void Synth::playSysexWithoutFraming(const Bit8u *sysex, Bit32u len) { - if (len < 4) { - printDebug("playSysexWithoutFraming: Message is too short (%d bytes)!", len); - return; - } - if (sysex[0] != SYSEX_MANUFACTURER_ROLAND) { - printDebug("playSysexWithoutFraming: Header not intended for this device manufacturer: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); - return; - } - if (sysex[2] == SYSEX_MDL_D50) { - printDebug("playSysexWithoutFraming: Header is intended for model D-50 (not yet supported): %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); - return; - } - else if (sysex[2] != SYSEX_MDL_MT32) { - printDebug("playSysexWithoutFraming: Header not intended for model MT-32: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); - return; - } - playSysexWithoutHeader(sysex[1], sysex[3], sysex + 4, len - 4); -} - -void Synth::playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len) { - if (device > 0x10) { - // We have device ID 0x10 (default, but changeable, on real MT-32), < 0x10 is for channels - printDebug("playSysexWithoutHeader: Message is not intended for this device ID (provided: %02x, expected: 0x10 or channel)", (int)device); - return; - } - if (len < 4) { - printDebug("playSysexWithoutHeader: Message is too short (%d bytes)!", len); - return; - } - unsigned char checksum = calcSysexChecksum(sysex, len - 1, 0); - if (checksum != sysex[len - 1]) { - printDebug("playSysexWithoutHeader: Message checksum is incorrect (provided: %02x, expected: %02x)!", sysex[len - 1], checksum); - return; - } - len -= 1; // Exclude checksum - switch (command) { - case SYSEX_CMD_DT1: - writeSysex(device, sysex, len); - break; - case SYSEX_CMD_RQ1: - readSysex(device, sysex, len); - break; - default: - printDebug("playSysexWithoutFraming: Unsupported command %02x", command); - return; - } -} - -void Synth::readSysex(unsigned char /*device*/, const Bit8u * /*sysex*/, Bit32u /*len*/) { -} - -const MemoryRegion memoryRegions[8] = { - {MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9}, - {MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85}, - {MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8}, - {MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128}, - {MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64}, - {MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::SystemArea), 1}, - {MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1}, - {MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1} -}; - -const int NUM_REGIONS = sizeof(memoryRegions) / sizeof(MemoryRegion); - -void Synth::writeSysex(unsigned char device, const Bit8u *sysex, Bit32u len) { - Bit32u addr = (sysex[0] << 16) | (sysex[1] << 8) | (sysex[2]); - addr = MT32EMU_MEMADDR(addr); - sysex += 3; - len -= 3; - //printDebug("Sysex addr: 0x%06x", MT32EMU_SYSEXMEMADDR(addr)); - // NOTE: Please keep both lower and upper bounds in each check, for ease of reading - - // Process channel-specific sysex by converting it to device-global - if (device < 0x10) { - printDebug("WRITE-CHANNEL: Channel %d temp area 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr)); - if (/*addr >= MT32EMU_MEMADDR(0x000000) && */addr < MT32EMU_MEMADDR(0x010000)) { - int offset; - if (chantable[device] == -1) { - printDebug(" (Channel not mapped to a partial... 0 offset)"); - offset = 0; - } else if (chantable[device] == 8) { - printDebug(" (Channel mapped to rhythm... 0 offset)"); - offset = 0; - } else { - offset = chantable[device] * sizeof(MemParams::PatchTemp); - printDebug(" (Setting extra offset to %d)", offset); - } - addr += MT32EMU_MEMADDR(0x030000) + offset; - } else if (/*addr >= 0x010000 && */ addr < MT32EMU_MEMADDR(0x020000)) { - addr += MT32EMU_MEMADDR(0x030110) - MT32EMU_MEMADDR(0x010000); - } else if (/*addr >= 0x020000 && */ addr < MT32EMU_MEMADDR(0x030000)) { - int offset; - if (chantable[device] == -1) { - printDebug(" (Channel not mapped to a partial... 0 offset)"); - offset = 0; - } else if (chantable[device] == 8) { - printDebug(" (Channel mapped to rhythm... 0 offset)"); - offset = 0; - } else { - offset = chantable[device] * sizeof(TimbreParam); - printDebug(" (Setting extra offset to %d)", offset); - } - addr += MT32EMU_MEMADDR(0x040000) - MT32EMU_MEMADDR(0x020000) + offset; - } else { - printDebug("PlaySysexWithoutHeader: Invalid channel %d address 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr)); - return; - } - } - - // Process device-global sysex (possibly converted from channel-specific sysex above) - for (;;) { - // Find the appropriate memory region - int regionNum; - const MemoryRegion *region = NULL; // Initialised to please compiler - for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) { - region = &memoryRegions[regionNum]; - if (region->contains(addr)) { - writeMemoryRegion(region, addr, region->getClampedLen(addr, len), sysex); - break; - } - } - if (regionNum == NUM_REGIONS) { - printDebug("Sysex write to unrecognised address %06x, len %d", MT32EMU_SYSEXMEMADDR(addr), len); - break; - } - Bit32u next = region->next(addr, len); - if (next == 0) { - break; - } - addr += next; - sysex += next; - len -= next; - } -} - -void Synth::readMemory(Bit32u addr, Bit32u len, Bit8u *data) { - int regionNum; - const MemoryRegion *region = NULL; - for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) { - region = &memoryRegions[regionNum]; - if (region->contains(addr)) { - readMemoryRegion(region, addr, len, data); - break; - } - } -} - -void Synth::readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data) { - unsigned int first = region->firstTouched(addr); - //unsigned int last = region->lastTouched(addr, len); - unsigned int off = region->firstTouchedOffset(addr); - len = region->getClampedLen(addr, len); - - unsigned int m; - - switch (region->type) { - case MR_PatchTemp: - for (m = 0; m < len; m++) - data[m] = ((Bit8u *)&mt32ram.patchSettings[first])[off + m]; - break; - case MR_RhythmTemp: - for (m = 0; m < len; m++) - data[m] = ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m]; - break; - case MR_TimbreTemp: - for (m = 0; m < len; m++) - data[m] = ((Bit8u *)&mt32ram.timbreSettings[first])[off + m]; - break; - case MR_Patches: - for (m = 0; m < len; m++) - data[m] = ((Bit8u *)&mt32ram.patches[first])[off + m]; - break; - case MR_Timbres: - for (m = 0; m < len; m++) - data[m] = ((Bit8u *)&mt32ram.timbres[first])[off + m]; - break; - case MR_System: - for (m = 0; m < len; m++) - data[m] = ((Bit8u *)&mt32ram.system)[m + off]; - break; - default: - for (m = 0; m < len; m += 2) { - data[m] = 0xff; - if (m + 1 < len) { - data[m+1] = (Bit8u)region->type; - } - } - // TODO: Don't care about the others ATM - break; - } - -} - -void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data) { - unsigned int first = region->firstTouched(addr); - unsigned int last = region->lastTouched(addr, len); - unsigned int off = region->firstTouchedOffset(addr); - switch (region->type) { - case MR_PatchTemp: - for (unsigned int m = 0; m < len; m++) { - ((Bit8u *)&mt32ram.patchSettings[first])[off + m] = data[m]; - } - //printDebug("Patch temp: Patch %d, offset %x, len %d", off/16, off % 16, len); - - for (unsigned int i = first; i <= last; i++) { - int absTimbreNum = mt32ram.patchSettings[i].patch.timbreGroup * 64 + mt32ram.patchSettings[i].patch.timbreNum; - char timbreName[11]; - memcpy(timbreName, mt32ram.timbres[absTimbreNum].timbre.common.name, 10); - timbreName[10] = 0; - printDebug("WRITE-PARTPATCH (%d-%d@%d..%d): %d; timbre=%d (%s), outlevel=%d", first, last, off, off + len, i, absTimbreNum, timbreName, mt32ram.patchSettings[i].outlevel); - if (parts[i] != NULL) { - if (i != 8) { - // Note: Confirmed on CM-64 that we definitely *should* update the timbre here, - // but only in the case that the sysex actually writes to those values - if (i == first && off > 2) { - printDebug(" (Not updating timbre, since those values weren't touched)"); - } else { - parts[i]->setTimbre(&mt32ram.timbres[parts[i]->getAbsTimbreNum()].timbre); - } - } - parts[i]->refresh(); - } - } - break; - case MR_RhythmTemp: - for (unsigned int m = 0; m < len; m++) - ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m] = data[m]; - for (unsigned int i = first; i <= last; i++) { - int timbreNum = mt32ram.rhythmSettings[i].timbre; - char timbreName[11]; - if (timbreNum < 94) { - memcpy(timbreName, mt32ram.timbres[128 + timbreNum].timbre.common.name, 10); - timbreName[10] = 0; - } else { - strcpy(timbreName, "[None]"); - } - printDebug("WRITE-RHYTHM (%d-%d@%d..%d): %d; level=%02x, panpot=%02x, reverb=%02x, timbre=%d (%s)", first, last, off, off + len, i, mt32ram.rhythmSettings[i].outlevel, mt32ram.rhythmSettings[i].panpot, mt32ram.rhythmSettings[i].reverbSwitch, mt32ram.rhythmSettings[i].timbre, timbreName); - } - if (parts[8] != NULL) { - parts[8]->refresh(); - } - break; - case MR_TimbreTemp: - for (unsigned int m = 0; m < len; m++) - ((Bit8u *)&mt32ram.timbreSettings[first])[off + m] = data[m]; - for (unsigned int i = first; i <= last; i++) { - char instrumentName[11]; - memcpy(instrumentName, mt32ram.timbreSettings[i].common.name, 10); - instrumentName[10] = 0; - printDebug("WRITE-PARTTIMBRE (%d-%d@%d..%d): timbre=%d (%s)", first, last, off, off + len, i, instrumentName); - if (parts[i] != NULL) { - parts[i]->refresh(); - } - } - break; - case MR_Patches: - for (unsigned int m = 0; m < len; m++) - ((Bit8u *)&mt32ram.patches[first])[off + m] = data[m]; - for (unsigned int i = first; i <= last; i++) { - PatchParam *patch = &mt32ram.patches[i]; - int patchAbsTimbreNum = patch->timbreGroup * 64 + patch->timbreNum; - char instrumentName[11]; - memcpy(instrumentName, mt32ram.timbres[patchAbsTimbreNum].timbre.common.name, 10); - instrumentName[10] = 0; - Bit8u *n = (Bit8u *)patch; - printDebug("WRITE-PATCH (%d-%d@%d..%d): %d; timbre=%d (%s) %02X%02X%02X%02X%02X%02X%02X%02X", first, last, off, off + len, i, patchAbsTimbreNum, instrumentName, n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7]); - // FIXME:KG: The below is definitely dodgy. We just guess that this is the patch that the part was using - // based on a timbre match (but many patches could have the same timbre!) - // If this refresh is really correct, we should store the patch number in use by each part. - /* - for (int part = 0; part < 8; part++) { - if (parts[part] != NULL) { - int partPatchAbsTimbreNum = mt32ram.patchSettings[part].patch.timbreGroup * 64 + mt32ram.patchSettings[part].patch.timbreNum; - if (parts[part]->getAbsTimbreNum() == patchAbsTimbreNum) { - parts[part]->setPatch(patch); - parts[part]->RefreshPatch(); - } - } - } - */ - } - break; - case MR_Timbres: - // Timbres - first += 128; - last += 128; - for (unsigned int m = 0; m < len; m++) - ((Bit8u *)&mt32ram.timbres[first])[off + m] = data[m]; - for (unsigned int i = first; i <= last; i++) { - char instrumentName[11]; - memcpy(instrumentName, mt32ram.timbres[i].timbre.common.name, 10); - instrumentName[10] = 0; - printDebug("WRITE-TIMBRE (%d-%d@%d..%d): %d; name=\"%s\"", first, last, off, off + len, i, instrumentName); - // FIXME:KG: Not sure if the stuff below should be done (for rhythm and/or parts)... - // Does the real MT-32 automatically do this? - for (unsigned int part = 0; part < 9; part++) { - if (parts[part] != NULL) { - parts[part]->refreshTimbre(i); - } - } - } - break; - case MR_System: - for (unsigned int m = 0; m < len; m++) - ((Bit8u *)&mt32ram.system)[m + off] = data[m]; - - report(ReportType_devReconfig, NULL); - - printDebug("WRITE-SYSTEM:"); - refreshSystem(); - break; - case MR_Display: - char buf[MAX_SYSEX_SIZE]; - memcpy(&buf, &data[0], len); - buf[len] = 0; - printDebug("WRITE-LCD: %s", buf); - report(ReportType_lcdMessage, buf); - break; - case MR_Reset: - printDebug("RESET"); - report(ReportType_devReset, NULL); - partialManager->deactivateAll(); - mt32ram = mt32default; - for (int i = 0; i < 9; i++) { - parts[i]->refresh(); - } - isEnabled = false; - break; - } -} - -bool Synth::refreshSystem() { - memset(chantable, -1, sizeof(chantable)); - - for (unsigned int i = 0; i < 9; i++) { - //LOG(LOG_MISC|LOG_ERROR,"Part %d set to MIDI channel %d",i,mt32ram.system.chanAssign[i]); - if (mt32ram.system.chanAssign[i] == 16 && parts[i] != NULL) { - parts[i]->allSoundOff(); - } else { - chantable[(int)mt32ram.system.chanAssign[i]] = (char)i; - } - } - //FIXME:KG: This is just an educated guess. - // The LAPC-I documentation claims a range of 427.5Hz-452.6Hz (similar to what we have here) - // The MT-32 documentation claims a range of 432.1Hz-457.6Hz - masterTune = 440.0f * powf(2.0f, (mt32ram.system.masterTune - 64.0f) / (128.0f * 12.0f)); - printDebug(" Master Tune: %f", (double)masterTune); - printDebug(" Reverb: mode=%d, time=%d, level=%d", mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel); - report(ReportType_newReverbMode, &mt32ram.system.reverbMode); - report(ReportType_newReverbTime, &mt32ram.system.reverbTime); - report(ReportType_newReverbLevel, &mt32ram.system.reverbLevel); - - if (myProp.useDefaultReverb) { - initReverb(mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel); - } else { - initReverb(myProp.reverbType, myProp.reverbTime, mt32ram.system.reverbLevel); - } - - Bit8u *rset = mt32ram.system.reserveSettings; - printDebug(" Partial reserve: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]); - int pr = partialManager->setReserve(rset); - if (pr != 32) - printDebug(" (Partial Reserve Table with less than 32 partials reserved!)"); - rset = mt32ram.system.chanAssign; - printDebug(" Part assign: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]); - printDebug(" Master volume: %d", mt32ram.system.masterVol); - masterVolume = (Bit16u)(mt32ram.system.masterVol * 32767 / 100); - if (!tables.init(this, pcmWaves, (float)myProp.sampleRate, masterTune)) { - report(ReportType_errorSampleRate, NULL); - return false; - } - return true; -} - -bool Synth::dumpTimbre(File *file, const TimbreParam *timbre, Bit32u address) { - // Sysex header - if (!file->writeBit8u(0xF0)) - return false; - if (!file->writeBit8u(0x41)) - return false; - if (!file->writeBit8u(0x10)) - return false; - if (!file->writeBit8u(0x16)) - return false; - if (!file->writeBit8u(0x12)) - return false; - - char lsb = (char)(address & 0x7f); - char isb = (char)((address >> 7) & 0x7f); - char msb = (char)(((address >> 14) & 0x7f) | 0x08); - - //Address - if (!file->writeBit8u(msb)) - return false; - if (!file->writeBit8u(isb)) - return false; - if (!file->writeBit8u(lsb)) - return false; - - //Data - if (file->write(timbre, 246) != 246) - return false; - - //Checksum - unsigned char checksum = calcSysexChecksum((const Bit8u *)timbre, 246, msb + isb + lsb); - if (!file->writeBit8u(checksum)) - return false; - - //End of sysex - if (!file->writeBit8u(0xF7)) - return false; - return true; -} - -int Synth::dumpTimbres(const char *filename, int start, int len) { - File *file = openFile(filename, File::OpenMode_write); - if (file == NULL) - return -1; - - for (int timbreNum = start; timbreNum < start + len; timbreNum++) { - int useaddr = (timbreNum - start) * 256; - TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; - if (!dumpTimbre(file, timbre, useaddr)) - break; - } - closeFile(file); - return 0; -} - -void ProduceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) { -#if MT32EMU_USE_MMX > 2 - //FIXME:KG: This appears to introduce crackle - int donelen = i386_produceOutput1(useBuf, stream, len, volume); - len -= donelen; - stream += donelen * 2; - useBuf += donelen * 2; -#endif - int end = len * 2; - while (end--) { - *stream = *stream + (Bit16s)(((Bit32s)*useBuf++ * (Bit32s)volume)>>15); - stream++; - } -} - -void Synth::render(Bit16s *stream, Bit32u len) { - memset(stream, 0, len * sizeof (Bit16s) * 2); - if (!isEnabled) - return; - while (len > 0) { - Bit32u thisLen = len > MAX_SAMPLE_OUTPUT ? MAX_SAMPLE_OUTPUT : len; - doRender(stream, thisLen); - len -= thisLen; - stream += 2 * thisLen; - } -} - -void Synth::doRender(Bit16s *stream, Bit32u len) { - partialManager->ageAll(); - - if (myProp.useReverb) { - for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (partialManager->shouldReverb(i)) { - if (partialManager->produceOutput(i, &tmpBuffer[0], len)) { - ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); - } - } - } - Bit32u m = 0; - for (unsigned int i = 0; i < len; i++) { - sndbufl[i] = (float)stream[m] / 32767.0f; - m++; - sndbufr[i] = (float)stream[m] / 32767.0f; - m++; - } - reverbModel->processreplace(sndbufl, sndbufr, outbufl, outbufr, len, 1); - m=0; - for (unsigned int i = 0; i < len; i++) { - stream[m] = (Bit16s)(outbufl[i] * 32767.0f); - m++; - stream[m] = (Bit16s)(outbufr[i] * 32767.0f); - m++; - } - for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (!partialManager->shouldReverb(i)) { - if (partialManager->produceOutput(i, &tmpBuffer[0], len)) { - ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); - } - } - } - } else { - for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (partialManager->produceOutput(i, &tmpBuffer[0], len)) - ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); - } - } - - partialManager->clearAlreadyOutputed(); - -#if MT32EMU_MONITOR_PARTIALS == 1 - samplepos += len; - if (samplepos > myProp.SampleRate * 5) { - samplepos = 0; - int partialUsage[9]; - partialManager->GetPerPartPartialUsage(partialUsage); - printDebug("1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d", partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7]); - printDebug("Rhythm: %02d TOTAL: %02d", partialUsage[8], MT32EMU_MAX_PARTIALS - partialManager->GetFreePartialCount()); - } -#endif -} - -const Partial *Synth::getPartial(unsigned int partialNum) const { - return partialManager->getPartial(partialNum); -} - -const Part *Synth::getPart(unsigned int partNum) const { - if (partNum > 8) - return NULL; - return parts[partNum]; -} - -} diff --git a/sound/softsynth/mt32/synth.h b/sound/softsynth/mt32/synth.h deleted file mode 100644 index 3fc303d322..0000000000 --- a/sound/softsynth/mt32/synth.h +++ /dev/null @@ -1,300 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef MT32EMU_SYNTH_H -#define MT32EMU_SYNTH_H - -#include "common/scummsys.h" - -class revmodel; - -namespace MT32Emu { - -class File; -class TableInitialiser; -class Partial; -class PartialManager; -class Part; - -enum ReportType { - // Errors - ReportType_errorControlROM = 1, - ReportType_errorPCMROM, - ReportType_errorSampleRate, - - // Progress - ReportType_progressInit, - - // HW spec - ReportType_availableSSE, - ReportType_available3DNow, - ReportType_usingSSE, - ReportType_using3DNow, - - // General info - ReportType_lcdMessage, - ReportType_devReset, - ReportType_devReconfig, - ReportType_newReverbMode, - ReportType_newReverbTime, - ReportType_newReverbLevel -}; - -struct SynthProperties { - // Sample rate to use in mixing - int sampleRate; - - // Flag to activate reverb. True = use reverb, False = no reverb - bool useReverb; - // True to use software set reverb settings, False to set reverb settings in - // following parameters - bool useDefaultReverb; - // When not using the default settings, this specifies one of the 4 reverb types - // 1 = Room 2 = Hall 3 = Plate 4 = Tap - unsigned char reverbType; - // This specifies the delay time, from 0-7 (not sure of the actual MT-32's measurement) - unsigned char reverbTime; - // This specifies the reverb level, from 0-7 (not sure of the actual MT-32's measurement) - unsigned char reverbLevel; - // The name of the directory in which the ROM and data files are stored (with trailing slash/backslash) - // Not used if "openFile" is set. May be NULL in any case. - char *baseDir; - // This is used as the first argument to all callbacks - void *userData; - // Callback for reporting various errors and information. May be NULL - int (*report)(void *userData, ReportType type, const void *reportData); - // Callback for debug messages, in vprintf() format - void (*printDebug)(void *userData, const char *fmt, va_list list); - // Callback for providing an implementation of File, opened and ready for use - // May be NULL, in which case a default implementation will be used. - File *(*openFile)(void *userData, const char *filename, File::OpenMode mode); - // Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted. - void (*closeFile)(void *userData, File *file); -}; - -// This is the specification of the Callback routine used when calling the RecalcWaveforms -// function -typedef void (*recalcStatusCallback)(int percDone); - -// This external function recreates the base waveform file (waveforms.raw) using a specifed -// sampling rate. The callback routine provides interactivity to let the user know what -// percentage is complete in regenerating the waveforms. When a NULL pointer is used as the -// callback routine, no status is reported. -bool RecalcWaveforms(char * baseDir, int sampRate, recalcStatusCallback callBack); - -typedef float (*iir_filter_type)(float input,float *hist1_ptr, float *coef_ptr); - -const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41; - -const Bit8u SYSEX_MDL_MT32 = 0x16; -const Bit8u SYSEX_MDL_D50 = 0x14; - -const Bit8u SYSEX_CMD_RQ1 = 0x11; // Request data #1 -const Bit8u SYSEX_CMD_DT1 = 0x12; // Data set 1 -const Bit8u SYSEX_CMD_WSD = 0x40; // Want to send data -const Bit8u SYSEX_CMD_RQD = 0x41; // Request data -const Bit8u SYSEX_CMD_DAT = 0x42; // Data set -const Bit8u SYSEX_CMD_ACK = 0x43; // Acknowledge -const Bit8u SYSEX_CMD_EOD = 0x45; // End of data -const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error -const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection - -const unsigned int CONTROL_ROM_SIZE = 64 * 1024; - -struct ControlROMPCMStruct -{ - Bit8u pos; - Bit8u len; - Bit8u pitchLSB; - Bit8u pitchMSB; -}; - -struct ControlROMMap { - Bit16u idPos; - Bit16u idLen; - const char *idBytes; - Bit16u pcmTable; - Bit16u pcmCount; - Bit16u timbreAMap; - Bit16u timbreAOffset; - Bit16u timbreBMap; - Bit16u timbreBOffset; - Bit16u timbreRMap; - Bit16u timbreRCount; - Bit16u rhythmSettings; - Bit16u rhythmSettingsCount; - Bit16u reserveSettings; - Bit16u panSettings; - Bit16u programSettings; -}; - -enum MemoryRegionType { - MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset -}; - -class MemoryRegion { -public: - MemoryRegionType type; - Bit32u startAddr, entrySize, entries; - - int lastTouched(Bit32u addr, Bit32u len) const { - return (offset(addr) + len - 1) / entrySize; - } - int firstTouchedOffset(Bit32u addr) const { - return offset(addr) % entrySize; - } - int firstTouched(Bit32u addr) const { - return offset(addr) / entrySize; - } - Bit32u regionEnd() const { - return startAddr + entrySize * entries; - } - bool contains(Bit32u addr) const { - return addr >= startAddr && addr < regionEnd(); - } - int offset(Bit32u addr) const { - return addr - startAddr; - } - Bit32u getClampedLen(Bit32u addr, Bit32u len) const { - if (addr + len > regionEnd()) - return regionEnd() - addr; - return len; - } - Bit32u next(Bit32u addr, Bit32u len) const { - if (addr + len > regionEnd()) { - return regionEnd() - addr; - } - return 0; - } -}; - - -class Synth { -friend class Part; -friend class RhythmPart; -friend class Partial; -friend class Tables; -private: - bool isEnabled; - - iir_filter_type iirFilter; - - PCMWaveEntry *pcmWaves; // Array - - const ControlROMMap *controlROMMap; - Bit8u controlROMData[CONTROL_ROM_SIZE]; - Bit16s *pcmROMData; - int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM - - Bit8s chantable[32]; - - #if MT32EMU_MONITOR_PARTIALS == 1 - static Bit32s samplepos = 0; - #endif - - Tables tables; - - MemParams mt32ram, mt32default; - - revmodel *reverbModel; - - float masterTune; - Bit16u masterVolume; - - bool isOpen; - - PartialManager *partialManager; - Part *parts[9]; - - Bit16s tmpBuffer[MAX_SAMPLE_OUTPUT * 2]; - float sndbufl[MAX_SAMPLE_OUTPUT]; - float sndbufr[MAX_SAMPLE_OUTPUT]; - float outbufl[MAX_SAMPLE_OUTPUT]; - float outbufr[MAX_SAMPLE_OUTPUT]; - - SynthProperties myProp; - - bool loadPreset(File *file); - void initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel); - void doRender(Bit16s * stream, Bit32u len); - - void playAddressedSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); - void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); - void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data); - void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data); - - bool loadControlROM(const char *filename); - bool loadPCMROM(const char *filename); - bool dumpTimbre(File *file, const TimbreParam *timbre, Bit32u addr); - int dumpTimbres(const char *filename, int start, int len); - - bool initPCMList(Bit16u mapAddress, Bit16u count); - bool initRhythmTimbres(Bit16u mapAddress, Bit16u count); - bool initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre); - bool initRhythmTimbre(int drumNum, const Bit8u *mem, unsigned int memLen); - bool refreshSystem(); - -protected: - int report(ReportType type, const void *reportData); - File *openFile(const char *filename, File::OpenMode mode); - void closeFile(File *file); - void printDebug(const char *fmt, ...) GCC_PRINTF(2, 3); - -public: - static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum); - - Synth(); - ~Synth(); - - // Used to initialise the MT-32. Must be called before any other function. - // Returns true if initialization was sucessful, otherwise returns false. - bool open(SynthProperties &useProp); - - // Closes the MT-32 and deallocates any memory used by the synthesizer - void close(void); - - // Sends a 4-byte MIDI message to the MT-32 for immediate playback - void playMsg(Bit32u msg); - void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity); - - // Sends a string of Sysex commands to the MT-32 for immediate interpretation - // The length is in bytes - void playSysex(const Bit8u *sysex, Bit32u len); - void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len); - void playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len); - void writeSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); - - // This callback routine is used to have the MT-32 generate samples to the specified - // output stream. The length is in whole samples, not bytes. (I.E. in 16-bit stereo, - // one sample is 4 bytes) - void render(Bit16s * stream, Bit32u len); - - const Partial *getPartial(unsigned int partialNum) const; - - void readMemory(Bit32u addr, Bit32u len, Bit8u *data); - - // partNum should be 0..7 for Part 1..8, or 8 for Rhythm - const Part *getPart(unsigned int partNum) const; -}; - -} - -#endif diff --git a/sound/softsynth/mt32/tables.cpp b/sound/softsynth/mt32/tables.cpp deleted file mode 100644 index eba4d2a520..0000000000 --- a/sound/softsynth/mt32/tables.cpp +++ /dev/null @@ -1,757 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include -#include -#include - -#include "mt32emu.h" - -#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__) -// Older versions of Mac OS X didn't supply a powf function, so using it -// will cause a binary incompatibility when trying to run a binary built -// on a newer OS X release on an olderr one. And Solaris 8 doesn't provide -// powf, floorf, fabsf etc. at all. -// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in -// libstdc++. math/stubs.o should be empty, but it comes with a symbol for -// powf, resulting in a linker error because of multiple definitions. -// Hence we re-define them here. The only potential drawback is that it -// might be a little bit slower this way. -#define powf(x,y) ((float)pow(x,y)) -#define floorf(x) ((float)floor(x)) -#define fabsf(x) ((float)fabs(x)) -#endif - -#define FIXEDPOINT_MAKE(x, point) ((Bit32u)((1 << point) * x)) - -namespace MT32Emu { - -//Amplitude time velocity follow exponential coefficients -static const double tvcatconst[5] = {0.0, 0.002791309, 0.005942882, 0.012652792, 0.026938637}; -static const double tvcatmult[5] = {1.0, 1.072662811, 1.169129367, 1.288579123, 1.229630539}; - -// These are division constants for the TVF depth key follow -static const Bit32u depexp[5] = {3000, 950, 485, 255, 138}; - -//Envelope time keyfollow exponential coefficients -static const double tkcatconst[5] = {0.0, 0.005853144, 0.011148054, 0.019086143, 0.043333215}; -static const double tkcatmult[5] = {1.0, 1.058245688, 1.048488989, 1.016049301, 1.097538067}; - -// Begin filter stuff - -// Pre-warp the coefficients of a numerator or denominator. -// Note that a0 is assumed to be 1, so there is no wrapping -// of it. -static void prewarp(double *a1, double *a2, double fc, double fs) { - double wp; - - wp = 2.0 * fs * tan(DOUBLE_PI * fc / fs); - - *a2 = *a2 / (wp * wp); - *a1 = *a1 / wp; -} - -// Transform the numerator and denominator coefficients -// of s-domain biquad section into corresponding -// z-domain coefficients. -// -// Store the 4 IIR coefficients in array pointed by coef -// in following order: -// beta1, beta2 (denominator) -// alpha1, alpha2 (numerator) -// -// Arguments: -// a0-a2 - s-domain numerator coefficients -// b0-b2 - s-domain denominator coefficients -// k - filter gain factor. initially set to 1 -// and modified by each biquad section in such -// a way, as to make it the coefficient by -// which to multiply the overall filter gain -// in order to achieve a desired overall filter gain, -// specified in initial value of k. -// fs - sampling rate (Hz) -// coef - array of z-domain coefficients to be filled in. -// -// Return: -// On return, set coef z-domain coefficients -static void bilinear(double a0, double a1, double a2, double b0, double b1, double b2, double *k, double fs, float *coef) { - double ad, bd; - - // alpha (Numerator in s-domain) - ad = 4. * a2 * fs * fs + 2. * a1 * fs + a0; - // beta (Denominator in s-domain) - bd = 4. * b2 * fs * fs + 2. * b1* fs + b0; - - // update gain constant for this section - *k *= ad/bd; - - // Denominator - *coef++ = (float)((2. * b0 - 8. * b2 * fs * fs) / bd); // beta1 - *coef++ = (float)((4. * b2 * fs * fs - 2. * b1 * fs + b0) / bd); // beta2 - - // Nominator - *coef++ = (float)((2. * a0 - 8. * a2 * fs * fs) / ad); // alpha1 - *coef = (float)((4. * a2 * fs * fs - 2. * a1 * fs + a0) / ad); // alpha2 -} - -// a0-a2: numerator coefficients -// b0-b2: denominator coefficients -// fc: Filter cutoff frequency -// fs: sampling rate -// k: overall gain factor -// coef: pointer to 4 iir coefficients -static void szxform(double *a0, double *a1, double *a2, double *b0, double *b1, double *b2, double fc, double fs, double *k, float *coef) { - // Calculate a1 and a2 and overwrite the original values - prewarp(a1, a2, fc, fs); - prewarp(b1, b2, fc, fs); - bilinear(*a0, *a1, *a2, *b0, *b1, *b2, k, fs, coef); -} - -static void initFilter(float fs, float fc, float *icoeff, float Q) { - float *coef; - double a0, a1, a2, b0, b1, b2; - - double k = 1.5; // Set overall filter gain factor - coef = icoeff + 1; // Skip k, or gain - - // Section 1 - a0 = 1.0; - a1 = 0; - a2 = 0; - b0 = 1.0; - b1 = 0.765367 / Q; // Divide by resonance or Q - b2 = 1.0; - szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef); - coef += 4; // Point to next filter section - - // Section 2 - a0 = 1.0; - a1 = 0; - a2 = 0; - b0 = 1.0; - b1 = 1.847759 / Q; - b2 = 1.0; - szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef); - - icoeff[0] = (float)k; -} - -void Tables::initFiltCoeff(float samplerate) { - for (int j = 0; j < FILTERGRAN; j++) { - for (int res = 0; res < 31; res++) { - float tres = resonanceFactor[res]; - initFilter((float)samplerate, (((float)(j+1.0)/FILTERGRAN)) * ((float)samplerate/2), filtCoeff[j][res], tres); - } - } -} - -void Tables::initEnvelopes(float samplerate) { - for (int lf = 0; lf <= 100; lf++) { - float elf = (float)lf; - - // General envelope - // This formula fits observation of the CM-32L by +/- 0.03s or so for the second time value in the filter, - // when all other times were 0 and all levels were 100. Note that variations occur depending on the level - // delta of the section, which we're not fully emulating. - float seconds = powf(2.0f, (elf / 8.0f) + 7.0f) / 32768.0f; - int samples = (int)(seconds * samplerate); - envTime[lf] = samples; - - // Cap on envelope times depending on the level delta - if (elf == 0) { - envDeltaMaxTime[lf] = 63; - } else { - float cap = 11.0f * (float)log(elf) + 64; - if (cap > 100.0f) { - cap = 100.0f; - } - envDeltaMaxTime[lf] = (int)cap; - } - - - // This (approximately) represents the time durations when the target level is 0. - // Not sure why this is a special case, but it's seen to be from the real thing. - seconds = powf(2, (elf / 8.0f) + 6) / 32768.0f; - envDecayTime[lf] = (int)(seconds * samplerate); - - // I am certain of this: Verified by hand LFO log - lfoPeriod[lf] = (Bit32u)(((float)samplerate) / (powf(1.088883372f, (float)lf) * 0.021236044f)); - } -} - -void Tables::initMT32ConstantTables(Synth *synth) { - int lf; - synth->printDebug("Initialising Pitch Tables"); - for (lf = -108; lf <= 108; lf++) { - tvfKeyfollowMult[lf + 108] = (int)(256 * powf(2.0f, (float)(lf / 24.0f))); - //synth->printDebug("KT %d = %d", f, keytable[f+108]); - } - - for (int res = 0; res < 31; res++) { - resonanceFactor[res] = powf((float)res / 30.0f, 5.0f) + 1.0f; - } - - int period = 65536; - - for (int ang = 0; ang < period; ang++) { - int halfang = (period / 2); - int angval = ang % halfang; - float tval = (((float)angval / (float)halfang) - 0.5f) * 2; - if (ang >= halfang) - tval = -tval; - sintable[ang] = (Bit16s)(tval * 50.0f) + 50; - } - - int velt, dep; - float tempdep; - for (velt = 0; velt < 128; velt++) { - for (dep = 0; dep < 5; dep++) { - if (dep > 0) { - float ff = (float)(exp(3.5f * tvcatconst[dep] * (59.0f - (float)velt)) * tvcatmult[dep]); - tempdep = 256.0f * ff; - envTimeVelfollowMult[dep][velt] = (int)tempdep; - //if ((velt % 16) == 0) { - // synth->printDebug("Key %d, depth %d, factor %d", velt, dep, (int)tempdep); - //} - } else - envTimeVelfollowMult[dep][velt] = 256; - } - - for (dep = -7; dep < 8; dep++) { - float fldep = (float)abs(dep) / 7.0f; - fldep = powf(fldep,2.5f); - if (dep < 0) - fldep = fldep * -1.0f; - pwVelfollowAdd[dep+7][velt] = Bit32s((fldep * (float)velt * 100) / 128.0); - } - } - - for (dep = 0; dep <= 100; dep++) { - for (velt = 0; velt < 128; velt++) { - float fdep = (float)dep * 0.000347013f; // Another MT-32 constant - float fv = ((float)velt - 64.0f)/7.26f; - float flogdep = powf(10, fdep * fv); - float fbase; - - if (velt > 64) - synth->tables.tvfVelfollowMult[velt][dep] = (int)(flogdep * 256.0); - else { - //lff = 1 - (pow(((128.0 - (float)lf) / 64.0),.25) * ((float)velt / 96)); - fbase = 1 - (powf(((float)dep / 100.0f),.25f) * ((float)(64-velt) / 96.0f)); - synth->tables.tvfVelfollowMult[velt][dep] = (int)(fbase * 256.0); - } - //synth->printDebug("Filvel dep %d velt %d = %x", dep, velt, filveltable[velt][dep]); - } - } - - for (lf = 0; lf < 128; lf++) { - float veloFract = lf / 127.0f; - for (int velsens = 0; velsens <= 100; velsens++) { - float sensFract = (velsens - 50) / 50.0f; - if (velsens < 50) { - tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, veloFract * -sensFract * 127.0f / 20.0f), 8); - } else { - tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, (1.0f - veloFract) * sensFract * 127.0f / 20.0f), 8); - } - } - } - - for (lf = 0; lf <= 100; lf++) { - // Converts the 0-100 range used by the MT-32 to volume multiplier - volumeMult[lf] = FIXEDPOINT_MAKE(powf((float)lf / 100.0f, FLOAT_LN), 7); - } - - for (lf = 0; lf <= 100; lf++) { - float mv = lf / 100.0f; - float pt = mv - 0.5f; - if (pt < 0) - pt = 0; - - // Original (CC version) - //pwFactor[lf] = (int)(pt * 210.04f) + 128; - - // Approximation from sample comparison - pwFactor[lf] = (int)(pt * 179.0f) + 128; - } - - for (unsigned int i = 0; i < MAX_SAMPLE_OUTPUT; i++) { - int myRand; - myRand = rand(); - //myRand = ((myRand - 16383) * 7168) >> 16; - // This one is slower but works with all values of RAND_MAX - myRand = (int)((myRand - RAND_MAX / 2) / (float)RAND_MAX * (7168 / 2)); - //FIXME:KG: Original ultimately set the lowest two bits to 0, for no obvious reason - noiseBuf[i] = (Bit16s)myRand; - } - - float tdist; - float padjtable[51]; - for (lf = 0; lf <= 50; lf++) { - if (lf == 0) - padjtable[lf] = 7; - else if (lf == 1) - padjtable[lf] = 6; - else if (lf == 2) - padjtable[lf] = 5; - else if (lf == 3) - padjtable[lf] = 4; - else if (lf == 4) - padjtable[lf] = 4 - (0.333333f); - else if (lf == 5) - padjtable[lf] = 4 - (0.333333f * 2); - else if (lf == 6) - padjtable[lf] = 3; - else if ((lf > 6) && (lf <= 12)) { - tdist = (lf-6.0f) / 6.0f; - padjtable[lf] = 3.0f - tdist; - } else if ((lf > 12) && (lf <= 25)) { - tdist = (lf - 12.0f) / 13.0f; - padjtable[lf] = 2.0f - tdist; - } else { - tdist = (lf - 25.0f) / 25.0f; - padjtable[lf] = 1.0f - tdist; - } - //synth->printDebug("lf %d = padj %f", lf, (double)padjtable[lf]); - } - - float lfp, depf, finalval, tlf; - int depat, pval, depti; - for (lf = 0; lf <= 10; lf++) { - // I believe the depth is cubed or something - - for (depat = 0; depat <= 100; depat++) { - if (lf > 0) { - depti = abs(depat - 50); - tlf = (float)lf - padjtable[depti]; - if (tlf < 0) - tlf = 0; - lfp = (float)exp(0.713619942f * tlf) / 407.4945111f; - - if (depat < 50) - finalval = 4096.0f * powf(2, -lfp); - else - finalval = 4096.0f * powf(2, lfp); - pval = (int)finalval; - - pitchEnvVal[lf][depat] = pval; - //synth->printDebug("lf %d depat %d pval %d tlf %f lfp %f", lf,depat,pval, (double)tlf, (double)lfp); - } else { - pitchEnvVal[lf][depat] = 4096; - //synth->printDebug("lf %d depat %d pval 4096", lf, depat); - } - } - } - for (lf = 0; lf <= 100; lf++) { - // It's linear - verified on MT-32 - one of the few things linear - lfp = ((float)lf * 0.1904f) / 310.55f; - - for (depat = 0; depat <= 100; depat++) { - depf = ((float)depat - 50.0f) / 50.0f; - //finalval = pow(2, lfp * depf * .5); - finalval = 4096.0f + (4096.0f * lfp * depf); - - pval = (int)finalval; - - lfoShift[lf][depat] = pval; - - //synth->printDebug("lf %d depat %d pval %x", lf,depat,pval); - } - } - - for (lf = 0; lf <= 12; lf++) { - for (int distval = 0; distval < 128; distval++) { - float amplog, dval; - if (lf == 0) { - amplog = 0; - dval = 1; - tvaBiasMult[lf][distval] = 256; - } else { - /* - amplog = powf(1.431817011f, (float)lf) / FLOAT_PI; - dval = ((128.0f - (float)distval) / 128.0f); - amplog = exp(amplog); - dval = powf(amplog, dval) / amplog; - tvaBiasMult[lf][distval] = (int)(dval * 256.0); - */ - // Lets assume for a second it's linear - - // Distance of full volume reduction - amplog = (float)(12.0f / (float)lf) * 24.0f; - if (distval > amplog) { - tvaBiasMult[lf][distval] = 0; - } else { - dval = (amplog - (float)distval) / amplog; - tvaBiasMult[lf][distval] = (int)(dval * 256.0f); - } - } - //synth->printDebug("Ampbias lf %d distval %d = %f (%x) %f", lf, distval, (double)dval, tvaBiasMult[lf][distval],(double)amplog); - } - } - - for (lf = 0; lf <= 14; lf++) { - for (int distval = 0; distval < 128; distval++) { - float filval = fabsf((float)((lf - 7) * 12) / 7.0f); - float amplog, dval; - if (lf == 7) { - amplog = 0; - dval = 1; - tvfBiasMult[lf][distval] = 256; - } else { - //amplog = pow(1.431817011, filval) / FLOAT_PI; - amplog = powf(1.531817011f, filval) / FLOAT_PI; - dval = (128.0f - (float)distval) / 128.0f; - amplog = (float)exp(amplog); - dval = powf(amplog,dval)/amplog; - if (lf < 8) { - tvfBiasMult[lf][distval] = (int)(dval * 256.0f); - } else { - dval = powf(dval, 0.3333333f); - if (dval < 0.01f) - dval = 0.01f; - dval = 1 / dval; - tvfBiasMult[lf][distval] = (int)(dval * 256.0f); - } - } - //synth->printDebug("Fbias lf %d distval %d = %f (%x) %f", lf, distval, (double)dval, tvfBiasMult[lf][distval],(double)amplog); - } - } -} - -// Per-note table initialisation follows - -static void initSaw(NoteLookup *noteLookup, Bit32s div2) { - int tmpdiv = div2 << 16; - for (int rsaw = 0; rsaw <= 100; rsaw++) { - float fsaw; - if (rsaw < 50) - fsaw = 50.0f; - else - fsaw = (float)rsaw; - - //(66 - (((A8 - 50) / 50) ^ 0.63) * 50) / 132 - float sawfact = (66.0f - (powf((fsaw - 50.0f) / 50.0f, 0.63f) * 50.0f)) / 132.0f; - noteLookup->sawTable[rsaw] = (int)(sawfact * (float)tmpdiv) >> 16; - //synth->printDebug("F %d divtable %d saw %d sawtable %d", f, div, rsaw, sawtable[f][rsaw]); - } -} - -static void initDep(KeyLookup *keyLookup, float f) { - for (int dep = 0; dep < 5; dep++) { - if (dep == 0) { - keyLookup->envDepthMult[dep] = 256; - keyLookup->envTimeMult[dep] = 256; - } else { - float depfac = 3000.0f; - float ff, tempdep; - depfac = (float)depexp[dep]; - - ff = (f - (float)MIDDLEC) / depfac; - tempdep = powf(2, ff) * 256.0f; - keyLookup->envDepthMult[dep] = (int)tempdep; - - ff = (float)(exp(tkcatconst[dep] * ((float)MIDDLEC - f)) * tkcatmult[dep]); - keyLookup->envTimeMult[dep] = (int)(ff * 256.0f); - } - } - //synth->printDebug("F %f d1 %x d2 %x d3 %x d4 %x d5 %x", (double)f, noteLookup->fildepTable[0], noteLookup->fildepTable[1], noteLookup->fildepTable[2], noteLookup->fildepTable[3], noteLookup->fildepTable[4]); -} - -Bit16s Tables::clampWF(Synth *synth, const char *n, float ampVal, double input) { - Bit32s x = (Bit32s)(input * ampVal); - if (x < -ampVal - 1) { - synth->printDebug("%s==%d<-WGAMP-1!", n, x); - x = (Bit32s)(-ampVal - 1); - } else if (x > ampVal) { - synth->printDebug("%s==%d>WGAMP!", n, x); - x = (Bit32s)ampVal; - } - return (Bit16s)x; -} - -File *Tables::initWave(Synth *synth, NoteLookup *noteLookup, float ampVal, float div2, File *file) { - int iDiv2 = (int)div2; - noteLookup->waveformSize[0] = iDiv2 << 1; - noteLookup->waveformSize[1] = iDiv2 << 1; - noteLookup->waveformSize[2] = iDiv2 << 2; - for (int i = 0; i < 3; i++) { - if (noteLookup->waveforms[i] == NULL) { - noteLookup->waveforms[i] = new Bit16s[noteLookup->waveformSize[i]]; - } - } - if (file != NULL) { - for (int i = 0; i < 3 && file != NULL; i++) { - size_t len = noteLookup->waveformSize[i]; - for (unsigned int j = 0; j < len; j++) { - if (!file->readBit16u((Bit16u *)¬eLookup->waveforms[i][j])) { - synth->printDebug("Error reading wave file cache!"); - file->close(); - file = NULL; - break; - } - } - } - } - if (file == NULL) { - double sd = DOUBLE_PI / div2; - - for (int fa = 0; fa < (iDiv2 << 1); fa++) { - // sa ranges from 0 to 2PI - double sa = fa * sd; - - // Calculate a sample for the bandlimited sawtooth wave - double saw = 0.0; - int sincs = iDiv2 >> 1; - double sinus = 1.0; - for (int sincNum = 1; sincNum <= sincs; sincNum++) { - saw += sin(sinus * sa) / sinus; - sinus++; - } - - // This works pretty well - // Multiplied by 0.84 so that the spikes caused by bandlimiting don't overdrive the amplitude - noteLookup->waveforms[0][fa] = clampWF(synth, "saw", ampVal, -saw / (0.5 * DOUBLE_PI) * 0.84); - noteLookup->waveforms[1][fa] = clampWF(synth, "cos", ampVal, -cos(sa / 2.0)); - noteLookup->waveforms[2][fa * 2] = clampWF(synth, "cosoff_0", ampVal, -cos(sa - DOUBLE_PI)); - noteLookup->waveforms[2][fa * 2 + 1] = clampWF(synth, "cosoff_1", ampVal, -cos((sa + (sd / 2)) - DOUBLE_PI)); - } - } - return file; -} - -static void initFiltTable(NoteLookup *noteLookup, float freq, float rate) { - for (int tr = 0; tr <= 200; tr++) { - float ftr = (float)tr; - - // Verified exact on MT-32 - if (tr > 100) - ftr = 100.0f + (powf((ftr - 100.0f) / 100.0f, 3.0f) * 100.0f); - - // I think this is the one - float brsq = powf(10.0f, (tr / 50.0f) - 1.0f); - noteLookup->filtTable[0][tr] = (int)((freq * brsq) / (rate / 2) * FILTERGRAN); - if (noteLookup->filtTable[0][tr]>=((FILTERGRAN*15)/16)) - noteLookup->filtTable[0][tr] = ((FILTERGRAN*15)/16); - - float brsa = powf(10.0f, ((tr / 55.0f) - 1.0f)) / 2.0f; - noteLookup->filtTable[1][tr] = (int)((freq * brsa) / (rate / 2) * FILTERGRAN); - if (noteLookup->filtTable[1][tr]>=((FILTERGRAN*15)/16)) - noteLookup->filtTable[1][tr] = ((FILTERGRAN*15)/16); - } -} - -static void initNFiltTable(NoteLookup *noteLookup, float freq, float rate) { - for (int cf = 0; cf <= 100; cf++) { - float cfmult = (float)cf; - - for (int tf = 0;tf <= 100; tf++) { - float tfadd = (float)tf; - - //float freqsum = exp((cfmult + tfadd) / 30.0f) / 4.0f; - //float freqsum = 0.15f * exp(0.45f * ((cfmult + tfadd) / 10.0f)); - - float freqsum = powf(2.0f, ((cfmult + tfadd) - 40.0f) / 16.0f); - - noteLookup->nfiltTable[cf][tf] = (int)((freq * freqsum) / (rate / 2) * FILTERGRAN); - if (noteLookup->nfiltTable[cf][tf] >= ((FILTERGRAN * 15) / 16)) - noteLookup->nfiltTable[cf][tf] = ((FILTERGRAN * 15) / 16); - } - } -} - -File *Tables::initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float masterTune, PCMWaveEntry *pcmWaves, File *file) { - float freq = (float)(masterTune * pow(2.0, ((double)note - MIDDLEA) / 12.0)); - float div2 = rate * 2.0f / freq; - noteLookup->div2 = (int)div2; - - if (noteLookup->div2 == 0) - noteLookup->div2 = 1; - - initSaw(noteLookup, noteLookup->div2); - - //synth->printDebug("Note %f; freq=%f, div=%f", (double)note, (double)freq, (double) rate / freq); - file = initWave(synth, noteLookup, WGAMP, div2, file); - - // Create the pitch tables - if (noteLookup->wavTable == NULL) - noteLookup->wavTable = new Bit32u[synth->controlROMMap->pcmCount]; - double rateMult = 32000.0 / rate; - double tuner = freq * 65536.0f; - for (int pc = 0; pc < synth->controlROMMap->pcmCount; pc++) { - noteLookup->wavTable[pc] = (int)(tuner / pcmWaves[pc].tune * rateMult); - } - - initFiltTable(noteLookup, freq, rate); - initNFiltTable(noteLookup, freq, rate); - return file; -} - -bool Tables::initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float masterTune) { - const char *NoteNames[12] = { - "C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B " - }; - char filename[64]; - int intRate = (int)rate; - char version[4] = {0, 0, 0, 5}; - sprintf(filename, "waveformcache-%d-%.2f.raw", intRate, (double)masterTune); - - File *file = NULL; - char header[20]; - memcpy(header, "MT32WAVE", 8); - int pos = 8; - // Version... - for (int i = 0; i < 4; i++) - header[pos++] = version[i]; - header[pos++] = (char)((intRate >> 24) & 0xFF); - header[pos++] = (char)((intRate >> 16) & 0xFF); - header[pos++] = (char)((intRate >> 8) & 0xFF); - header[pos++] = (char)(intRate & 0xFF); - int intTuning = (int)masterTune; - header[pos++] = (char)((intTuning >> 8) & 0xFF); - header[pos++] = (char)(intTuning & 0xFF); - header[pos++] = 0; - header[pos] = (char)((masterTune - intTuning) * 10); -#if MT32EMU_WAVECACHEMODE < 2 - bool reading = false; - file = synth->openFile(filename, File::OpenMode_read); - if (file != NULL) { - char fileHeader[20]; - if (file->read(fileHeader, 20) == 20) { - if (memcmp(fileHeader, header, 20) == 0) { - Bit16u endianCheck; - if (file->readBit16u(&endianCheck)) { - if (endianCheck == 1) { - reading = true; - } else { - synth->printDebug("Endian check in %s does not match expected", filename); - } - } else { - synth->printDebug("Unable to read endian check in %s", filename); - } - } else { - synth->printDebug("Header of %s does not match expected", filename); - } - } else { - synth->printDebug("Error reading 16 bytes of %s", filename); - } - if (!reading) { - file->close(); - file = NULL; - } - } else { - synth->printDebug("Unable to open %s for reading", filename); - } -#endif - - float progress = 0.0f; - bool abort = false; - synth->report(ReportType_progressInit, &progress); - for (int f = LOWEST_NOTE; f <= HIGHEST_NOTE; f++) { - synth->printDebug("Initialising note %s%d", NoteNames[f % 12], (f / 12) - 2); - NoteLookup *noteLookup = ¬eLookups[f - LOWEST_NOTE]; - file = initNote(synth, noteLookup, (float)f, rate, masterTune, pcmWaves, file); - progress = (f - LOWEST_NOTE + 1) / (float)NUM_NOTES; - abort = synth->report(ReportType_progressInit, &progress) != 0; - if (abort) - break; - } - -#if MT32EMU_WAVECACHEMODE == 0 || MT32EMU_WAVECACHEMODE == 2 - if (file == NULL) { - file = synth->openFile(filename, File::OpenMode_write); - if (file != NULL) { - if (file->write(header, 20) == 20 && file->writeBit16u(1)) { - for (int f = 0; f < NUM_NOTES; f++) { - for (int i = 0; i < 3 && file != NULL; i++) { - int len = noteLookups[f].waveformSize[i]; - for (int j = 0; j < len; j++) { - if (!file->writeBit16u(noteLookups[f].waveforms[i][j])) { - synth->printDebug("Error writing waveform cache file"); - file->close(); - file = NULL; - break; - } - } - } - } - } else { - synth->printDebug("Error writing 16-byte header to %s - won't continue saving", filename); - } - } else { - synth->printDebug("Unable to open %s for writing - won't be created", filename); - } - } -#endif - - if (file != NULL) - synth->closeFile(file); - return !abort; -} - -void Tables::freeNotes() { - for (int t = 0; t < 3; t++) { - for (int m = 0; m < NUM_NOTES; m++) { - if (noteLookups[m].waveforms[t] != NULL) { - delete[] noteLookups[m].waveforms[t]; - noteLookups[m].waveforms[t] = NULL; - noteLookups[m].waveformSize[t] = 0; - } - if (noteLookups[m].wavTable != NULL) { - delete[] noteLookups[m].wavTable; - noteLookups[m].wavTable = NULL; - } - } - } - initialisedMasterTune = 0.0f; -} - -Tables::Tables() { - initialisedSampleRate = 0.0f; - initialisedMasterTune = 0.0f; - memset(¬eLookups, 0, sizeof(noteLookups)); -} - -bool Tables::init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune) { - if (sampleRate <= 0.0f) { - synth->printDebug("Bad sampleRate (%f <= 0.0f)", (double)sampleRate); - return false; - } - if (initialisedSampleRate == 0.0f) { - initMT32ConstantTables(synth); - } - if (initialisedSampleRate != sampleRate) { - initFiltCoeff(sampleRate); - initEnvelopes(sampleRate); - for (int key = 12; key <= 108; key++) { - initDep(&keyLookups[key - 12], (float)key); - } - } - if (initialisedSampleRate != sampleRate || initialisedMasterTune != masterTune) { - freeNotes(); - if (!initNotes(synth, pcmWaves, sampleRate, masterTune)) { - return false; - } - initialisedSampleRate = sampleRate; - initialisedMasterTune = masterTune; - } - return true; -} - -} diff --git a/sound/softsynth/mt32/tables.h b/sound/softsynth/mt32/tables.h deleted file mode 100644 index d9af5114b2..0000000000 --- a/sound/softsynth/mt32/tables.h +++ /dev/null @@ -1,116 +0,0 @@ -/* Copyright (c) 2003-2005 Various contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef MT32EMU_TABLES_H -#define MT32EMU_TABLES_H - -namespace MT32Emu { - -// Mathematical constants -const double DOUBLE_PI = 3.1415926535897932384626433832795; -const double DOUBLE_LN = 2.3025850929940456840179914546844; -const float FLOAT_PI = 3.1415926535897932384626433832795f; -const float FLOAT_LN = 2.3025850929940456840179914546844f; - -// Filter settings -const int FILTERGRAN = 512; - -// Amplitude of waveform generator -// FIXME: This value is the amplitude possible whilst avoiding -// overdriven values immediately after filtering when playing -// back SQ3MT.MID. Needs to be checked. -const int WGAMP = 12382; - -const int MIDDLEC = 60; -const int MIDDLEA = 69; // By this I mean "A above middle C" - -// FIXME:KG: may only need to do 12 to 108 -// 12..108 is the range allowed by note on commands, but the key can be modified by pitch keyfollow -// and adjustment for timbre pitch, so the results can be outside that range. -// Should we move it (by octave) into the 12..108 range, or keep it in 0..127 range, -// or something else altogether? -const int LOWEST_NOTE = 12; -const int HIGHEST_NOTE = 127; -const int NUM_NOTES = HIGHEST_NOTE - LOWEST_NOTE + 1; // Number of slots for note LUT - -class Synth; - -struct NoteLookup { - Bit32u div2; - Bit32u *wavTable; - Bit32s sawTable[101]; - int filtTable[2][201]; - int nfiltTable[101][101]; - Bit16s *waveforms[3]; - Bit32u waveformSize[3]; -}; - -struct KeyLookup { - Bit32s envTimeMult[5]; // For envelope time adjustment for key pressed - Bit32s envDepthMult[5]; -}; - -class Tables { - float initialisedSampleRate; - float initialisedMasterTune; - void initMT32ConstantTables(Synth *synth); - static Bit16s clampWF(Synth *synth, const char *n, float ampVal, double input); - static File *initWave(Synth *synth, NoteLookup *noteLookup, float ampsize, float div2, File *file); - bool initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float tuning); - void initEnvelopes(float sampleRate); - void initFiltCoeff(float samplerate); -public: - // Constant LUTs - Bit32s tvfKeyfollowMult[217]; - Bit32s tvfVelfollowMult[128][101]; - Bit32s tvfBiasMult[15][128]; - Bit32u tvaVelfollowMult[128][101]; - Bit32s tvaBiasMult[13][128]; - Bit16s noiseBuf[MAX_SAMPLE_OUTPUT]; - Bit16s sintable[65536]; - Bit32s pitchEnvVal[16][101]; - Bit32s envTimeVelfollowMult[5][128]; - Bit32s pwVelfollowAdd[15][128]; - float resonanceFactor[31]; - Bit32u lfoShift[101][101]; - Bit32s pwFactor[101]; - Bit32s volumeMult[101]; - - // LUTs varying with sample rate - Bit32u envTime[101]; - Bit32u envDeltaMaxTime[101]; - Bit32u envDecayTime[101]; - Bit32u lfoPeriod[101]; - float filtCoeff[FILTERGRAN][31][8]; - - // Various LUTs for each note and key - NoteLookup noteLookups[NUM_NOTES]; - KeyLookup keyLookups[97]; - - Tables(); - bool init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune); - File *initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float tuning, PCMWaveEntry *pcmWaves, File *file); - void freeNotes(); -}; - -} - -#endif diff --git a/sound/softsynth/opl/dbopl.cpp b/sound/softsynth/opl/dbopl.cpp deleted file mode 100644 index 47e263b6b9..0000000000 --- a/sound/softsynth/opl/dbopl.cpp +++ /dev/null @@ -1,1536 +0,0 @@ -/* - * Copyright (C) 2002-2010 The DOSBox Team - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -/* - DOSBox implementation of a combined Yamaha YMF262 and Yamaha YM3812 emulator. - Enabling the opl3 bit will switch the emulator to stereo opl3 output instead of regular mono opl2 - Except for the table generation it's all integer math - Can choose different types of generators, using muls and bigger tables, try different ones for slower platforms - The generation was based on the MAME implementation but tried to have it use less memory and be faster in general - MAME uses much bigger envelope tables and this will be the biggest cause of it sounding different at times - - //TODO Don't delay first operator 1 sample in opl3 mode - //TODO Maybe not use class method pointers but a regular function pointers with operator as first parameter - //TODO Fix panning for the Percussion channels, would any opl3 player use it and actually really change it though? - //TODO Check if having the same accuracy in all frequency multipliers sounds better or not - - //DUNNO Keyon in 4op, switch to 2op without keyoff. -*/ - -// Last synch with DOSBox SVN trunk r3556 - -#include "dbopl.h" - -#ifndef DISABLE_DOSBOX_OPL - -namespace OPL { -namespace DOSBox { - -#ifndef PI -#define PI 3.14159265358979323846 -#endif - -namespace DBOPL { - -#define OPLRATE ((double)(14318180.0 / 288.0)) -#define TREMOLO_TABLE 52 - -//Try to use most precision for frequencies -//Else try to keep different waves in synch -//#define WAVE_PRECISION 1 -#ifndef WAVE_PRECISION -//Wave bits available in the top of the 32bit range -//Original adlib uses 10.10, we use 10.22 -#define WAVE_BITS 10 -#else -//Need some extra bits at the top to have room for octaves and frequency multiplier -//We support to 8 times lower rate -//128 * 15 * 8 = 15350, 2^13.9, so need 14 bits -#define WAVE_BITS 14 -#endif -#define WAVE_SH ( 32 - WAVE_BITS ) -#define WAVE_MASK ( ( 1 << WAVE_SH ) - 1 ) - -//Use the same accuracy as the waves -#define LFO_SH ( WAVE_SH - 10 ) -//LFO is controlled by our tremolo 256 sample limit -#define LFO_MAX ( 256 << ( LFO_SH ) ) - - -//Maximum amount of attenuation bits -//Envelope goes to 511, 9 bits -#if (DBOPL_WAVE == WAVE_TABLEMUL ) -//Uses the value directly -#define ENV_BITS ( 9 ) -#else -//Add 3 bits here for more accuracy and would have to be shifted up either way -#define ENV_BITS ( 9 ) -#endif -//Limits of the envelope with those bits and when the envelope goes silent -#define ENV_MIN 0 -#define ENV_EXTRA ( ENV_BITS - 9 ) -#define ENV_MAX ( 511 << ENV_EXTRA ) -#define ENV_LIMIT ( ( 12 * 256) >> ( 3 - ENV_EXTRA ) ) -#define ENV_SILENT( _X_ ) ( (_X_) >= ENV_LIMIT ) - -//Attack/decay/release rate counter shift -#define RATE_SH 24 -#define RATE_MASK ( ( 1 << RATE_SH ) - 1 ) -//Has to fit within 16bit lookuptable -#define MUL_SH 16 - -//Check some ranges -#if ENV_EXTRA > 3 -#error Too many envelope bits -#endif - - -//How much to substract from the base value for the final attenuation -static const Bit8u KslCreateTable[16] = { - //0 will always be be lower than 7 * 8 - 64, 32, 24, 19, - 16, 12, 11, 10, - 8, 6, 5, 4, - 3, 2, 1, 0, -}; - -#define M(_X_) ((Bit8u)( (_X_) * 2)) -static const Bit8u FreqCreateTable[16] = { - M(0.5), M(1 ), M(2 ), M(3 ), M(4 ), M(5 ), M(6 ), M(7 ), - M(8 ), M(9 ), M(10), M(10), M(12), M(12), M(15), M(15) -}; -#undef M - -//We're not including the highest attack rate, that gets a special value -static const Bit8u AttackSamplesTable[13] = { - 69, 55, 46, 40, - 35, 29, 23, 20, - 19, 15, 11, 10, - 9 -}; -//On a real opl these values take 8 samples to reach and are based upon larger tables -static const Bit8u EnvelopeIncreaseTable[13] = { - 4, 5, 6, 7, - 8, 10, 12, 14, - 16, 20, 24, 28, - 32, -}; - -#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) -static Bit16u ExpTable[ 256 ]; -#endif - -#if ( DBOPL_WAVE == WAVE_HANDLER ) -//PI table used by WAVEHANDLER -static Bit16u SinTable[ 512 ]; -#endif - -#if ( DBOPL_WAVE > WAVE_HANDLER ) -//Layout of the waveform table in 512 entry intervals -//With overlapping waves we reduce the table to half it's size - -// | |//\\|____|WAV7|//__|/\ |____|/\/\| -// |\\//| | |WAV7| | \/| | | -// |06 |0126|17 |7 |3 |4 |4 5 |5 | - -//6 is just 0 shifted and masked - -static Bit16s WaveTable[ 8 * 512 ]; -//Distance into WaveTable the wave starts -static const Bit16u WaveBaseTable[8] = { - 0x000, 0x200, 0x200, 0x800, - 0xa00, 0xc00, 0x100, 0x400, - -}; -//Mask the counter with this -static const Bit16u WaveMaskTable[8] = { - 1023, 1023, 511, 511, - 1023, 1023, 512, 1023, -}; - -//Where to start the counter on at keyon -static const Bit16u WaveStartTable[8] = { - 512, 0, 0, 0, - 0, 512, 512, 256, -}; -#endif - -#if ( DBOPL_WAVE == WAVE_TABLEMUL ) -static Bit16u MulTable[ 384 ]; -#endif - -static Bit8u KslTable[ 8 * 16 ]; -static Bit8u TremoloTable[ TREMOLO_TABLE ]; -//Start of a channel behind the chip struct start -static Bit16u ChanOffsetTable[32]; -//Start of an operator behind the chip struct start -static Bit16u OpOffsetTable[64]; - -//The lower bits are the shift of the operator vibrato value -//The highest bit is right shifted to generate -1 or 0 for negation -//So taking the highest input value of 7 this gives 3, 7, 3, 0, -3, -7, -3, 0 -static const Bit8s VibratoTable[ 8 ] = { - 1 - 0x00, 0 - 0x00, 1 - 0x00, 30 - 0x00, - 1 - 0x80, 0 - 0x80, 1 - 0x80, 30 - 0x80 -}; - -//Shift strength for the ksl value determined by ksl strength -static const Bit8u KslShiftTable[4] = { - 31,1,2,0 -}; - -//Generate a table index and table shift value using input value from a selected rate -static void EnvelopeSelect( Bit8u val, Bit8u& index, Bit8u& shift ) { - if ( val < 13 * 4 ) { //Rate 0 - 12 - shift = 12 - ( val >> 2 ); - index = val & 3; - } else if ( val < 15 * 4 ) { //rate 13 - 14 - shift = 0; - index = val - 12 * 4; - } else { //rate 15 and up - shift = 0; - index = 12; - } -} - -#if ( DBOPL_WAVE == WAVE_HANDLER ) -/* - Generate the different waveforms out of the sine/exponetial table using handlers -*/ -static inline Bits MakeVolume( Bitu wave, Bitu volume ) { - Bitu total = wave + volume; - Bitu index = total & 0xff; - Bitu sig = ExpTable[ index ]; - Bitu exp = total >> 8; -#if 0 - //Check if we overflow the 31 shift limit - if ( exp >= 32 ) { - LOG_MSG( "WTF %d %d", total, exp ); - } -#endif - return (sig >> exp); -} - -static Bits DB_FASTCALL WaveForm0( Bitu i, Bitu volume ) { - Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 - Bitu wave = SinTable[i & 511]; - return (MakeVolume( wave, volume ) ^ neg) - neg; -} -static Bits DB_FASTCALL WaveForm1( Bitu i, Bitu volume ) { - Bit32u wave = SinTable[i & 511]; - wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); - return MakeVolume( wave, volume ); -} -static Bits DB_FASTCALL WaveForm2( Bitu i, Bitu volume ) { - Bitu wave = SinTable[i & 511]; - return MakeVolume( wave, volume ); -} -static Bits DB_FASTCALL WaveForm3( Bitu i, Bitu volume ) { - Bitu wave = SinTable[i & 255]; - wave |= ( ( (i ^ 256 ) & 256) - 1) >> ( 32 - 12 ); - return MakeVolume( wave, volume ); -} -static Bits DB_FASTCALL WaveForm4( Bitu i, Bitu volume ) { - //Twice as fast - i <<= 1; - Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 - Bitu wave = SinTable[i & 511]; - wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); - return (MakeVolume( wave, volume ) ^ neg) - neg; -} -static Bits DB_FASTCALL WaveForm5( Bitu i, Bitu volume ) { - //Twice as fast - i <<= 1; - Bitu wave = SinTable[i & 511]; - wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); - return MakeVolume( wave, volume ); -} -static Bits DB_FASTCALL WaveForm6( Bitu i, Bitu volume ) { - Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 - return (MakeVolume( 0, volume ) ^ neg) - neg; -} -static Bits DB_FASTCALL WaveForm7( Bitu i, Bitu volume ) { - //Negative is reversed here - Bits neg = (( i >> 9) & 1) - 1; - Bitu wave = (i << 3); - //When negative the volume also runs backwards - wave = ((wave ^ neg) - neg) & 4095; - return (MakeVolume( wave, volume ) ^ neg) - neg; -} - -static const WaveHandler WaveHandlerTable[8] = { - WaveForm0, WaveForm1, WaveForm2, WaveForm3, - WaveForm4, WaveForm5, WaveForm6, WaveForm7 -}; - -#endif - -/* - Operator -*/ - -//We zero out when rate == 0 -inline void Operator::UpdateAttack( const Chip* chip ) { - Bit8u rate = reg60 >> 4; - if ( rate ) { - Bit8u val = (rate << 2) + ksr; - attackAdd = chip->attackRates[ val ]; - rateZero &= ~(1 << ATTACK); - } else { - attackAdd = 0; - rateZero |= (1 << ATTACK); - } -} -inline void Operator::UpdateDecay( const Chip* chip ) { - Bit8u rate = reg60 & 0xf; - if ( rate ) { - Bit8u val = (rate << 2) + ksr; - decayAdd = chip->linearRates[ val ]; - rateZero &= ~(1 << DECAY); - } else { - decayAdd = 0; - rateZero |= (1 << DECAY); - } -} -inline void Operator::UpdateRelease( const Chip* chip ) { - Bit8u rate = reg80 & 0xf; - if ( rate ) { - Bit8u val = (rate << 2) + ksr; - releaseAdd = chip->linearRates[ val ]; - rateZero &= ~(1 << RELEASE); - if ( !(reg20 & MASK_SUSTAIN ) ) { - rateZero &= ~( 1 << SUSTAIN ); - } - } else { - rateZero |= (1 << RELEASE); - releaseAdd = 0; - if ( !(reg20 & MASK_SUSTAIN ) ) { - rateZero |= ( 1 << SUSTAIN ); - } - } -} - -inline void Operator::UpdateAttenuation( ) { - Bit8u kslBase = (Bit8u)((chanData >> SHIFT_KSLBASE) & 0xff); - Bit32u tl = reg40 & 0x3f; - Bit8u kslShift = KslShiftTable[ reg40 >> 6 ]; - //Make sure the attenuation goes to the right bits - totalLevel = tl << ( ENV_BITS - 7 ); //Total level goes 2 bits below max - totalLevel += ( kslBase << ENV_EXTRA ) >> kslShift; -} - -void Operator::UpdateFrequency( ) { - Bit32u freq = chanData & (( 1 << 10 ) - 1); - Bit32u block = (chanData >> 10) & 0xff; -#ifdef WAVE_PRECISION - block = 7 - block; - waveAdd = ( freq * freqMul ) >> block; -#else - waveAdd = ( freq << block ) * freqMul; -#endif - if ( reg20 & MASK_VIBRATO ) { - vibStrength = (Bit8u)(freq >> 7); - -#ifdef WAVE_PRECISION - vibrato = ( vibStrength * freqMul ) >> block; -#else - vibrato = ( vibStrength << block ) * freqMul; -#endif - } else { - vibStrength = 0; - vibrato = 0; - } -} - -void Operator::UpdateRates( const Chip* chip ) { - //Mame seems to reverse this where enabling ksr actually lowers - //the rate, but pdf manuals says otherwise? - Bit8u newKsr = (Bit8u)((chanData >> SHIFT_KEYCODE) & 0xff); - if ( !( reg20 & MASK_KSR ) ) { - newKsr >>= 2; - } - if ( ksr == newKsr ) - return; - ksr = newKsr; - UpdateAttack( chip ); - UpdateDecay( chip ); - UpdateRelease( chip ); -} - -INLINE Bit32s Operator::RateForward( Bit32u add ) { - rateIndex += add; - Bit32s ret = rateIndex >> RATE_SH; - rateIndex = rateIndex & RATE_MASK; - return ret; -} - -template< Operator::State yes> -Bits Operator::TemplateVolume( ) { - Bit32s vol = volume; - Bit32s change; - switch ( yes ) { - case OFF: - return ENV_MAX; - case ATTACK: - change = RateForward( attackAdd ); - if ( !change ) - return vol; - vol += ( (~vol) * change ) >> 3; - if ( vol < ENV_MIN ) { - volume = ENV_MIN; - rateIndex = 0; - SetState( DECAY ); - return ENV_MIN; - } - break; - case DECAY: - vol += RateForward( decayAdd ); - if ( GCC_UNLIKELY(vol >= sustainLevel) ) { - //Check if we didn't overshoot max attenuation, then just go off - if ( GCC_UNLIKELY(vol >= ENV_MAX) ) { - volume = ENV_MAX; - SetState( OFF ); - return ENV_MAX; - } - //Continue as sustain - rateIndex = 0; - SetState( SUSTAIN ); - } - break; - case SUSTAIN: - if ( reg20 & MASK_SUSTAIN ) { - return vol; - } - //In sustain phase, but not sustaining, do regular release - case RELEASE: - vol += RateForward( releaseAdd ); - if ( GCC_UNLIKELY(vol >= ENV_MAX) ) { - volume = ENV_MAX; - SetState( OFF ); - return ENV_MAX; - } - break; - } - volume = vol; - return vol; -} - -static const VolumeHandler VolumeHandlerTable[5] = { - &Operator::TemplateVolume< Operator::OFF >, - &Operator::TemplateVolume< Operator::RELEASE >, - &Operator::TemplateVolume< Operator::SUSTAIN >, - &Operator::TemplateVolume< Operator::DECAY >, - &Operator::TemplateVolume< Operator::ATTACK > -}; - -INLINE Bitu Operator::ForwardVolume() { - return currentLevel + (this->*volHandler)(); -} - - -INLINE Bitu Operator::ForwardWave() { - waveIndex += waveCurrent; - return waveIndex >> WAVE_SH; -} - -void Operator::Write20( const Chip* chip, Bit8u val ) { - Bit8u change = (reg20 ^ val ); - if ( !change ) - return; - reg20 = val; - //Shift the tremolo bit over the entire register, saved a branch, YES! - tremoloMask = (Bit8s)(val) >> 7; - tremoloMask &= ~(( 1 << ENV_EXTRA ) -1); - //Update specific features based on changes - if ( change & MASK_KSR ) { - UpdateRates( chip ); - } - //With sustain enable the volume doesn't change - if ( reg20 & MASK_SUSTAIN || ( !releaseAdd ) ) { - rateZero |= ( 1 << SUSTAIN ); - } else { - rateZero &= ~( 1 << SUSTAIN ); - } - //Frequency multiplier or vibrato changed - if ( change & (0xf | MASK_VIBRATO) ) { - freqMul = chip->freqMul[ val & 0xf ]; - UpdateFrequency(); - } -} - -void Operator::Write40( const Chip* /*chip*/, Bit8u val ) { - if (!(reg40 ^ val )) - return; - reg40 = val; - UpdateAttenuation( ); -} - -void Operator::Write60( const Chip* chip, Bit8u val ) { - Bit8u change = reg60 ^ val; - reg60 = val; - if ( change & 0x0f ) { - UpdateDecay( chip ); - } - if ( change & 0xf0 ) { - UpdateAttack( chip ); - } -} - -void Operator::Write80( const Chip* chip, Bit8u val ) { - Bit8u change = (reg80 ^ val ); - if ( !change ) - return; - reg80 = val; - Bit8u sustain = val >> 4; - //Turn 0xf into 0x1f - sustain |= ( sustain + 1) & 0x10; - sustainLevel = sustain << ( ENV_BITS - 5 ); - if ( change & 0x0f ) { - UpdateRelease( chip ); - } -} - -void Operator::WriteE0( const Chip* chip, Bit8u val ) { - if ( !(regE0 ^ val) ) - return; - //in opl3 mode you can always selet 7 waveforms regardless of waveformselect - Bit8u waveForm = val & ( ( 0x3 & chip->waveFormMask ) | (0x7 & chip->opl3Active ) ); - regE0 = val; -#if ( DBOPL_WAVE == WAVE_HANDLER ) - waveHandler = WaveHandlerTable[ waveForm ]; -#else - waveBase = WaveTable + WaveBaseTable[ waveForm ]; - waveStart = WaveStartTable[ waveForm ] << WAVE_SH; - waveMask = WaveMaskTable[ waveForm ]; -#endif -} - -INLINE void Operator::SetState( Bit8u s ) { - state = s; - volHandler = VolumeHandlerTable[ s ]; -} - -INLINE bool Operator::Silent() const { - if ( !ENV_SILENT( totalLevel + volume ) ) - return false; - if ( !(rateZero & ( 1 << state ) ) ) - return false; - return true; -} - -INLINE void Operator::Prepare( const Chip* chip ) { - currentLevel = totalLevel + (chip->tremoloValue & tremoloMask); - waveCurrent = waveAdd; - if ( vibStrength >> chip->vibratoShift ) { - Bit32s add = vibrato >> chip->vibratoShift; - //Sign extend over the shift value - Bit32s neg = chip->vibratoSign; - //Negate the add with -1 or 0 - add = ( add ^ neg ) - neg; - waveCurrent += add; - } -} - -void Operator::KeyOn( Bit8u mask ) { - if ( !keyOn ) { - //Restart the frequency generator -#if ( DBOPL_WAVE > WAVE_HANDLER ) - waveIndex = waveStart; -#else - waveIndex = 0; -#endif - rateIndex = 0; - SetState( ATTACK ); - } - keyOn |= mask; -} - -void Operator::KeyOff( Bit8u mask ) { - keyOn &= ~mask; - if ( !keyOn ) { - if ( state != OFF ) { - SetState( RELEASE ); - } - } -} - -INLINE Bits Operator::GetWave( Bitu index, Bitu vol ) { -#if ( DBOPL_WAVE == WAVE_HANDLER ) - return waveHandler( index, vol << ( 3 - ENV_EXTRA ) ); -#elif ( DBOPL_WAVE == WAVE_TABLEMUL ) - return (waveBase[ index & waveMask ] * MulTable[ vol >> ENV_EXTRA ]) >> MUL_SH; -#elif ( DBOPL_WAVE == WAVE_TABLELOG ) - Bit32s wave = waveBase[ index & waveMask ]; - Bit32u total = ( wave & 0x7fff ) + ( vol << ( 3 - ENV_EXTRA ) ); - Bit32s sig = ExpTable[ total & 0xff ]; - Bit32u exp = total >> 8; - Bit32s neg = wave >> 16; - return ((sig ^ neg) - neg) >> exp; -#else -#error "No valid wave routine" -#endif -} - -INLINE Bits Operator::GetSample( Bits modulation ) { - Bitu vol = ForwardVolume(); - if ( ENV_SILENT( vol ) ) { - //Simply forward the wave - waveIndex += waveCurrent; - return 0; - } else { - Bitu index = ForwardWave(); - index += modulation; - return GetWave( index, vol ); - } -} - -Operator::Operator() { - chanData = 0; - freqMul = 0; - waveIndex = 0; - waveAdd = 0; - waveCurrent = 0; - keyOn = 0; - ksr = 0; - reg20 = 0; - reg40 = 0; - reg60 = 0; - reg80 = 0; - regE0 = 0; - SetState( OFF ); - rateZero = (1 << OFF); - sustainLevel = ENV_MAX; - currentLevel = ENV_MAX; - totalLevel = ENV_MAX; - volume = ENV_MAX; - releaseAdd = 0; -} - -/* - Channel -*/ - -Channel::Channel() { - old[0] = old[1] = 0; - chanData = 0; - regB0 = 0; - regC0 = 0; - maskLeft = -1; - maskRight = -1; - feedback = 31; - fourMask = 0; - synthHandler = &Channel::BlockTemplate< sm2FM >; -} - -void Channel::SetChanData( const Chip* chip, Bit32u data ) { - Bit32u change = chanData ^ data; - chanData = data; - Op( 0 )->chanData = data; - Op( 1 )->chanData = data; - //Since a frequency update triggered this, always update frequency - Op( 0 )->UpdateFrequency(); - Op( 1 )->UpdateFrequency(); - if ( change & ( 0xff << SHIFT_KSLBASE ) ) { - Op( 0 )->UpdateAttenuation(); - Op( 1 )->UpdateAttenuation(); - } - if ( change & ( 0xff << SHIFT_KEYCODE ) ) { - Op( 0 )->UpdateRates( chip ); - Op( 1 )->UpdateRates( chip ); - } -} - -void Channel::UpdateFrequency( const Chip* chip, Bit8u fourOp ) { - //Extrace the frequency bits - Bit32u data = chanData & 0xffff; - Bit32u kslBase = KslTable[ data >> 6 ]; - Bit32u keyCode = ( data & 0x1c00) >> 9; - if ( chip->reg08 & 0x40 ) { - keyCode |= ( data & 0x100)>>8; /* notesel == 1 */ - } else { - keyCode |= ( data & 0x200)>>9; /* notesel == 0 */ - } - //Add the keycode and ksl into the highest bits of chanData - data |= (keyCode << SHIFT_KEYCODE) | ( kslBase << SHIFT_KSLBASE ); - ( this + 0 )->SetChanData( chip, data ); - if ( fourOp & 0x3f ) { - ( this + 1 )->SetChanData( chip, data ); - } -} - -void Channel::WriteA0( const Chip* chip, Bit8u val ) { - Bit8u fourOp = chip->reg104 & chip->opl3Active & fourMask; - //Don't handle writes to silent fourop channels - if ( fourOp > 0x80 ) - return; - Bit32u change = (chanData ^ val ) & 0xff; - if ( change ) { - chanData ^= change; - UpdateFrequency( chip, fourOp ); - } -} - -void Channel::WriteB0( const Chip* chip, Bit8u val ) { - Bit8u fourOp = chip->reg104 & chip->opl3Active & fourMask; - //Don't handle writes to silent fourop channels - if ( fourOp > 0x80 ) - return; - Bitu change = (chanData ^ ( val << 8 ) ) & 0x1f00; - if ( change ) { - chanData ^= change; - UpdateFrequency( chip, fourOp ); - } - //Check for a change in the keyon/off state - if ( !(( val ^ regB0) & 0x20)) - return; - regB0 = val; - if ( val & 0x20 ) { - Op(0)->KeyOn( 0x1 ); - Op(1)->KeyOn( 0x1 ); - if ( fourOp & 0x3f ) { - ( this + 1 )->Op(0)->KeyOn( 1 ); - ( this + 1 )->Op(1)->KeyOn( 1 ); - } - } else { - Op(0)->KeyOff( 0x1 ); - Op(1)->KeyOff( 0x1 ); - if ( fourOp & 0x3f ) { - ( this + 1 )->Op(0)->KeyOff( 1 ); - ( this + 1 )->Op(1)->KeyOff( 1 ); - } - } -} - -void Channel::WriteC0( const Chip* chip, Bit8u val ) { - Bit8u change = val ^ regC0; - if ( !change ) - return; - regC0 = val; - feedback = ( val >> 1 ) & 7; - if ( feedback ) { - //We shift the input to the right 10 bit wave index value - feedback = 9 - feedback; - } else { - feedback = 31; - } - //Select the new synth mode - if ( chip->opl3Active ) { - //4-op mode enabled for this channel - if ( (chip->reg104 & fourMask) & 0x3f ) { - Channel* chan0, *chan1; - //Check if it's the 2nd channel in a 4-op - if ( !(fourMask & 0x80 ) ) { - chan0 = this; - chan1 = this + 1; - } else { - chan0 = this - 1; - chan1 = this; - } - - Bit8u synth = ( (chan0->regC0 & 1) << 0 )| (( chan1->regC0 & 1) << 1 ); - switch ( synth ) { - case 0: - chan0->synthHandler = &Channel::BlockTemplate< sm3FMFM >; - break; - case 1: - chan0->synthHandler = &Channel::BlockTemplate< sm3AMFM >; - break; - case 2: - chan0->synthHandler = &Channel::BlockTemplate< sm3FMAM >; - break; - case 3: - chan0->synthHandler = &Channel::BlockTemplate< sm3AMAM >; - break; - } - //Disable updating percussion channels - } else if ((fourMask & 0x40) && ( chip->regBD & 0x20) ) { - - //Regular dual op, am or fm - } else if ( val & 1 ) { - synthHandler = &Channel::BlockTemplate< sm3AM >; - } else { - synthHandler = &Channel::BlockTemplate< sm3FM >; - } - maskLeft = ( val & 0x10 ) ? -1 : 0; - maskRight = ( val & 0x20 ) ? -1 : 0; - //opl2 active - } else { - //Disable updating percussion channels - if ( (fourMask & 0x40) && ( chip->regBD & 0x20 ) ) { - - //Regular dual op, am or fm - } else if ( val & 1 ) { - synthHandler = &Channel::BlockTemplate< sm2AM >; - } else { - synthHandler = &Channel::BlockTemplate< sm2FM >; - } - } -} - -void Channel::ResetC0( const Chip* chip ) { - Bit8u val = regC0; - regC0 ^= 0xff; - WriteC0( chip, val ); -} - -template< bool opl3Mode> -INLINE void Channel::GeneratePercussion( Chip* chip, Bit32s* output ) { - Channel* chan = this; - - //BassDrum - Bit32s mod = (Bit32u)((old[0] + old[1])) >> feedback; - old[0] = old[1]; - old[1] = Op(0)->GetSample( mod ); - - //When bassdrum is in AM mode first operator is ignoed - if ( chan->regC0 & 1 ) { - mod = 0; - } else { - mod = old[0]; - } - Bit32s sample = Op(1)->GetSample( mod ); - - - //Precalculate stuff used by other outputs - Bit32u noiseBit = chip->ForwardNoise() & 0x1; - Bit32u c2 = Op(2)->ForwardWave(); - Bit32u c5 = Op(5)->ForwardWave(); - Bit32u phaseBit = (((c2 & 0x88) ^ ((c2<<5) & 0x80)) | ((c5 ^ (c5<<2)) & 0x20)) ? 0x02 : 0x00; - - //Hi-Hat - Bit32u hhVol = Op(2)->ForwardVolume(); - if ( !ENV_SILENT( hhVol ) ) { - Bit32u hhIndex = (phaseBit<<8) | (0x34 << ( phaseBit ^ (noiseBit << 1 ))); - sample += Op(2)->GetWave( hhIndex, hhVol ); - } - //Snare Drum - Bit32u sdVol = Op(3)->ForwardVolume(); - if ( !ENV_SILENT( sdVol ) ) { - Bit32u sdIndex = ( 0x100 + (c2 & 0x100) ) ^ ( noiseBit << 8 ); - sample += Op(3)->GetWave( sdIndex, sdVol ); - } - //Tom-tom - sample += Op(4)->GetSample( 0 ); - - //Top-Cymbal - Bit32u tcVol = Op(5)->ForwardVolume(); - if ( !ENV_SILENT( tcVol ) ) { - Bit32u tcIndex = (1 + phaseBit) << 8; - sample += Op(5)->GetWave( tcIndex, tcVol ); - } - sample <<= 1; - if ( opl3Mode ) { - output[0] += sample; - output[1] += sample; - } else { - output[0] += sample; - } -} - -template -Channel* Channel::BlockTemplate( Chip* chip, Bit32u samples, Bit32s* output ) { - switch( mode ) { - case sm2AM: - case sm3AM: - if ( Op(0)->Silent() && Op(1)->Silent() ) { - old[0] = old[1] = 0; - return (this + 1); - } - break; - case sm2FM: - case sm3FM: - if ( Op(1)->Silent() ) { - old[0] = old[1] = 0; - return (this + 1); - } - break; - case sm3FMFM: - if ( Op(3)->Silent() ) { - old[0] = old[1] = 0; - return (this + 2); - } - break; - case sm3AMFM: - if ( Op(0)->Silent() && Op(3)->Silent() ) { - old[0] = old[1] = 0; - return (this + 2); - } - break; - case sm3FMAM: - if ( Op(1)->Silent() && Op(3)->Silent() ) { - old[0] = old[1] = 0; - return (this + 2); - } - break; - case sm3AMAM: - if ( Op(0)->Silent() && Op(2)->Silent() && Op(3)->Silent() ) { - old[0] = old[1] = 0; - return (this + 2); - } - break; - case sm2Percussion: - // This case was not handled in the DOSBox code either - // thus we leave this blank. - // TODO: Consider checking this. - break; - case sm3Percussion: - // This case was not handled in the DOSBox code either - // thus we leave this blank. - // TODO: Consider checking this. - break; - case sm4Start: - // This case was not handled in the DOSBox code either - // thus we leave this blank. - // TODO: Consider checking this. - break; - case sm6Start: - // This case was not handled in the DOSBox code either - // thus we leave this blank. - // TODO: Consider checking this. - break; - } - //Init the operators with the the current vibrato and tremolo values - Op( 0 )->Prepare( chip ); - Op( 1 )->Prepare( chip ); - if ( mode > sm4Start ) { - Op( 2 )->Prepare( chip ); - Op( 3 )->Prepare( chip ); - } - if ( mode > sm6Start ) { - Op( 4 )->Prepare( chip ); - Op( 5 )->Prepare( chip ); - } - for ( Bitu i = 0; i < samples; i++ ) { - //Early out for percussion handlers - if ( mode == sm2Percussion ) { - GeneratePercussion( chip, output + i ); - continue; //Prevent some unitialized value bitching - } else if ( mode == sm3Percussion ) { - GeneratePercussion( chip, output + i * 2 ); - continue; //Prevent some unitialized value bitching - } - - //Do unsigned shift so we can shift out all bits but still stay in 10 bit range otherwise - Bit32s mod = (Bit32u)((old[0] + old[1])) >> feedback; - old[0] = old[1]; - old[1] = Op(0)->GetSample( mod ); - Bit32s sample; - Bit32s out0 = old[0]; - if ( mode == sm2AM || mode == sm3AM ) { - sample = out0 + Op(1)->GetSample( 0 ); - } else if ( mode == sm2FM || mode == sm3FM ) { - sample = Op(1)->GetSample( out0 ); - } else if ( mode == sm3FMFM ) { - Bits next = Op(1)->GetSample( out0 ); - next = Op(2)->GetSample( next ); - sample = Op(3)->GetSample( next ); - } else if ( mode == sm3AMFM ) { - sample = out0; - Bits next = Op(1)->GetSample( 0 ); - next = Op(2)->GetSample( next ); - sample += Op(3)->GetSample( next ); - } else if ( mode == sm3FMAM ) { - sample = Op(1)->GetSample( out0 ); - Bits next = Op(2)->GetSample( 0 ); - sample += Op(3)->GetSample( next ); - } else if ( mode == sm3AMAM ) { - sample = out0; - Bits next = Op(1)->GetSample( 0 ); - sample += Op(2)->GetSample( next ); - sample += Op(3)->GetSample( 0 ); - } - switch( mode ) { - case sm2AM: - case sm2FM: - output[ i ] += sample; - break; - case sm3AM: - case sm3FM: - case sm3FMFM: - case sm3AMFM: - case sm3FMAM: - case sm3AMAM: - output[ i * 2 + 0 ] += sample & maskLeft; - output[ i * 2 + 1 ] += sample & maskRight; - break; - case sm2Percussion: - // This case was not handled in the DOSBox code either - // thus we leave this blank. - // TODO: Consider checking this. - break; - case sm3Percussion: - // This case was not handled in the DOSBox code either - // thus we leave this blank. - // TODO: Consider checking this. - break; - case sm4Start: - // This case was not handled in the DOSBox code either - // thus we leave this blank. - // TODO: Consider checking this. - break; - case sm6Start: - // This case was not handled in the DOSBox code either - // thus we leave this blank. - // TODO: Consider checking this. - break; - } - } - switch( mode ) { - case sm2AM: - case sm2FM: - case sm3AM: - case sm3FM: - return ( this + 1 ); - case sm3FMFM: - case sm3AMFM: - case sm3FMAM: - case sm3AMAM: - return( this + 2 ); - case sm2Percussion: - case sm3Percussion: - return( this + 3 ); - case sm4Start: - // This case was not handled in the DOSBox code either - // thus we leave this blank. - // TODO: Consider checking this. - break; - case sm6Start: - // This case was not handled in the DOSBox code either - // thus we leave this blank. - // TODO: Consider checking this. - break; - } - return 0; -} - -/* - Chip -*/ - -Chip::Chip() { - reg08 = 0; - reg04 = 0; - regBD = 0; - reg104 = 0; - opl3Active = 0; -} - -INLINE Bit32u Chip::ForwardNoise() { - noiseCounter += noiseAdd; - Bitu count = noiseCounter >> LFO_SH; - noiseCounter &= WAVE_MASK; - for ( ; count > 0; --count ) { - //Noise calculation from mame - noiseValue ^= ( 0x800302 ) & ( 0 - (noiseValue & 1 ) ); - noiseValue >>= 1; - } - return noiseValue; -} - -INLINE Bit32u Chip::ForwardLFO( Bit32u samples ) { - //Current vibrato value, runs 4x slower than tremolo - vibratoSign = ( VibratoTable[ vibratoIndex >> 2] ) >> 7; - vibratoShift = ( VibratoTable[ vibratoIndex >> 2] & 7) + vibratoStrength; - tremoloValue = TremoloTable[ tremoloIndex ] >> tremoloStrength; - - //Check hom many samples there can be done before the value changes - Bit32u todo = LFO_MAX - lfoCounter; - Bit32u count = (todo + lfoAdd - 1) / lfoAdd; - if ( count > samples ) { - count = samples; - lfoCounter += count * lfoAdd; - } else { - lfoCounter += count * lfoAdd; - lfoCounter &= (LFO_MAX - 1); - //Maximum of 7 vibrato value * 4 - vibratoIndex = ( vibratoIndex + 1 ) & 31; - //Clip tremolo to the the table size - if ( tremoloIndex + 1 < TREMOLO_TABLE ) - ++tremoloIndex; - else - tremoloIndex = 0; - } - return count; -} - - -void Chip::WriteBD( Bit8u val ) { - Bit8u change = regBD ^ val; - if ( !change ) - return; - regBD = val; - //TODO could do this with shift and xor? - vibratoStrength = (val & 0x40) ? 0x00 : 0x01; - tremoloStrength = (val & 0x80) ? 0x00 : 0x02; - if ( val & 0x20 ) { - //Drum was just enabled, make sure channel 6 has the right synth - if ( change & 0x20 ) { - if ( opl3Active ) { - chan[6].synthHandler = &Channel::BlockTemplate< sm3Percussion >; - } else { - chan[6].synthHandler = &Channel::BlockTemplate< sm2Percussion >; - } - } - //Bass Drum - if ( val & 0x10 ) { - chan[6].op[0].KeyOn( 0x2 ); - chan[6].op[1].KeyOn( 0x2 ); - } else { - chan[6].op[0].KeyOff( 0x2 ); - chan[6].op[1].KeyOff( 0x2 ); - } - //Hi-Hat - if ( val & 0x1 ) { - chan[7].op[0].KeyOn( 0x2 ); - } else { - chan[7].op[0].KeyOff( 0x2 ); - } - //Snare - if ( val & 0x8 ) { - chan[7].op[1].KeyOn( 0x2 ); - } else { - chan[7].op[1].KeyOff( 0x2 ); - } - //Tom-Tom - if ( val & 0x4 ) { - chan[8].op[0].KeyOn( 0x2 ); - } else { - chan[8].op[0].KeyOff( 0x2 ); - } - //Top Cymbal - if ( val & 0x2 ) { - chan[8].op[1].KeyOn( 0x2 ); - } else { - chan[8].op[1].KeyOff( 0x2 ); - } - //Toggle keyoffs when we turn off the percussion - } else if ( change & 0x20 ) { - //Trigger a reset to setup the original synth handler - chan[6].ResetC0( this ); - chan[6].op[0].KeyOff( 0x2 ); - chan[6].op[1].KeyOff( 0x2 ); - chan[7].op[0].KeyOff( 0x2 ); - chan[7].op[1].KeyOff( 0x2 ); - chan[8].op[0].KeyOff( 0x2 ); - chan[8].op[1].KeyOff( 0x2 ); - } -} - - -#define REGOP( _FUNC_ ) \ - index = ( ( reg >> 3) & 0x20 ) | ( reg & 0x1f ); \ - if ( OpOffsetTable[ index ] ) { \ - Operator* regOp = (Operator*)( ((char *)this ) + OpOffsetTable[ index ] ); \ - regOp->_FUNC_( this, val ); \ - } - -#define REGCHAN( _FUNC_ ) \ - index = ( ( reg >> 4) & 0x10 ) | ( reg & 0xf ); \ - if ( ChanOffsetTable[ index ] ) { \ - Channel* regChan = (Channel*)( ((char *)this ) + ChanOffsetTable[ index ] ); \ - regChan->_FUNC_( this, val ); \ - } - -void Chip::WriteReg( Bit32u reg, Bit8u val ) { - Bitu index; - switch ( (reg & 0xf0) >> 4 ) { - case 0x00 >> 4: - if ( reg == 0x01 ) { - waveFormMask = ( val & 0x20 ) ? 0x7 : 0x0; - } else if ( reg == 0x104 ) { - //Only detect changes in lowest 6 bits - if ( !((reg104 ^ val) & 0x3f) ) - return; - //Always keep the highest bit enabled, for checking > 0x80 - reg104 = 0x80 | ( val & 0x3f ); - } else if ( reg == 0x105 ) { - //MAME says the real opl3 doesn't reset anything on opl3 disable/enable till the next write in another register - if ( !((opl3Active ^ val) & 1 ) ) - return; - opl3Active = ( val & 1 ) ? 0xff : 0; - //Update the 0xc0 register for all channels to signal the switch to mono/stereo handlers - for ( int i = 0; i < 18;i++ ) { - chan[i].ResetC0( this ); - } - } else if ( reg == 0x08 ) { - reg08 = val; - } - case 0x10 >> 4: - break; - case 0x20 >> 4: - case 0x30 >> 4: - REGOP( Write20 ); - break; - case 0x40 >> 4: - case 0x50 >> 4: - REGOP( Write40 ); - break; - case 0x60 >> 4: - case 0x70 >> 4: - REGOP( Write60 ); - break; - case 0x80 >> 4: - case 0x90 >> 4: - REGOP( Write80 ); - break; - case 0xa0 >> 4: - REGCHAN( WriteA0 ); - break; - case 0xb0 >> 4: - if ( reg == 0xbd ) { - WriteBD( val ); - } else { - REGCHAN( WriteB0 ); - } - break; - case 0xc0 >> 4: - REGCHAN( WriteC0 ); - case 0xd0 >> 4: - break; - case 0xe0 >> 4: - case 0xf0 >> 4: - REGOP( WriteE0 ); - break; - } -} - - -Bit32u Chip::WriteAddr( Bit32u port, Bit8u val ) { - switch ( port & 3 ) { - case 0: - return val; - case 2: - if ( opl3Active || (val == 0x05) ) - return 0x100 | val; - else - return val; - } - return 0; -} - -void Chip::GenerateBlock2( Bitu total, Bit32s* output ) { - while ( total > 0 ) { - Bit32u samples = ForwardLFO( total ); - memset(output, 0, sizeof(Bit32s) * samples); - int count = 0; - for( Channel* ch = chan; ch < chan + 9; ) { - count++; - ch = (ch->*(ch->synthHandler))( this, samples, output ); - } - total -= samples; - output += samples; - } -} - -void Chip::GenerateBlock3( Bitu total, Bit32s* output ) { - while ( total > 0 ) { - Bit32u samples = ForwardLFO( total ); - memset(output, 0, sizeof(Bit32s) * 2 * samples); - int count = 0; - for( Channel* ch = chan; ch < chan + 18; ) { - count++; - ch = (ch->*(ch->synthHandler))( this, samples, output ); - } - total -= samples; - output += samples * 2; - } -} - -void Chip::Setup( Bit32u rate ) { - double scale = OPLRATE / (double)rate; - - //Noise counter is run at the same precision as general waves - noiseAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) ); - noiseCounter = 0; - noiseValue = 1; //Make sure it triggers the noise xor the first time - //The low frequency oscillation counter - //Every time his overflows vibrato and tremoloindex are increased - lfoAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) ); - lfoCounter = 0; - vibratoIndex = 0; - tremoloIndex = 0; - - //With higher octave this gets shifted up - //-1 since the freqCreateTable = *2 -#ifdef WAVE_PRECISION - double freqScale = ( 1 << 7 ) * scale * ( 1 << ( WAVE_SH - 1 - 10)); - for ( int i = 0; i < 16; i++ ) { - freqMul[i] = (Bit32u)( 0.5 + freqScale * FreqCreateTable[ i ] ); - } -#else - Bit32u freqScale = (Bit32u)( 0.5 + scale * ( 1 << ( WAVE_SH - 1 - 10))); - for ( int i = 0; i < 16; i++ ) { - freqMul[i] = freqScale * FreqCreateTable[ i ]; - } -#endif - - //-3 since the real envelope takes 8 steps to reach the single value we supply - for ( Bit8u i = 0; i < 76; i++ ) { - Bit8u index, shift; - EnvelopeSelect( i, index, shift ); - linearRates[i] = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH + ENV_EXTRA - shift - 3 ))); - } - //Generate the best matching attack rate - for ( Bit8u i = 0; i < 62; i++ ) { - Bit8u index, shift; - EnvelopeSelect( i, index, shift ); - //Original amount of samples the attack would take - Bit32s original = (Bit32u)( (AttackSamplesTable[ index ] << shift) / scale); - - Bit32s guessAdd = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH - shift - 3 ))); - Bit32s bestAdd = guessAdd; - Bit32u bestDiff = 1 << 30; - for( Bit32u passes = 0; passes < 16; passes ++ ) { - Bit32s volume = ENV_MAX; - Bit32s samples = 0; - Bit32u count = 0; - while ( volume > 0 && samples < original * 2 ) { - count += guessAdd; - Bit32s change = count >> RATE_SH; - count &= RATE_MASK; - if ( GCC_UNLIKELY(change) ) { // less than 1 % - volume += ( ~volume * change ) >> 3; - } - samples++; - - } - Bit32s diff = original - samples; - Bit32u lDiff = labs( diff ); - //Init last on first pass - if ( lDiff < bestDiff ) { - bestDiff = lDiff; - bestAdd = guessAdd; - if ( !bestDiff ) - break; - } - //Below our target - if ( diff < 0 ) { - //Better than the last time - Bit32s mul = ((original - diff) << 12) / original; - guessAdd = ((guessAdd * mul) >> 12); - guessAdd++; - } else if ( diff > 0 ) { - Bit32s mul = ((original - diff) << 12) / original; - guessAdd = (guessAdd * mul) >> 12; - guessAdd--; - } - } - attackRates[i] = bestAdd; - } - for ( Bit8u i = 62; i < 76; i++ ) { - //This should provide instant volume maximizing - attackRates[i] = 8 << RATE_SH; - } - //Setup the channels with the correct four op flags - //Channels are accessed through a table so they appear linear here - chan[ 0].fourMask = 0x00 | ( 1 << 0 ); - chan[ 1].fourMask = 0x80 | ( 1 << 0 ); - chan[ 2].fourMask = 0x00 | ( 1 << 1 ); - chan[ 3].fourMask = 0x80 | ( 1 << 1 ); - chan[ 4].fourMask = 0x00 | ( 1 << 2 ); - chan[ 5].fourMask = 0x80 | ( 1 << 2 ); - - chan[ 9].fourMask = 0x00 | ( 1 << 3 ); - chan[10].fourMask = 0x80 | ( 1 << 3 ); - chan[11].fourMask = 0x00 | ( 1 << 4 ); - chan[12].fourMask = 0x80 | ( 1 << 4 ); - chan[13].fourMask = 0x00 | ( 1 << 5 ); - chan[14].fourMask = 0x80 | ( 1 << 5 ); - - //mark the percussion channels - chan[ 6].fourMask = 0x40; - chan[ 7].fourMask = 0x40; - chan[ 8].fourMask = 0x40; - - //Clear Everything in opl3 mode - WriteReg( 0x105, 0x1 ); - for ( int i = 0; i < 512; i++ ) { - if ( i == 0x105 ) - continue; - WriteReg( i, 0xff ); - WriteReg( i, 0x0 ); - } - WriteReg( 0x105, 0x0 ); - //Clear everything in opl2 mode - for ( int i = 0; i < 255; i++ ) { - WriteReg( i, 0xff ); - WriteReg( i, 0x0 ); - } -} - -static bool doneTables = false; -void InitTables( void ) { - if ( doneTables ) - return; - doneTables = true; -#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) - //Exponential volume table, same as the real adlib - for ( int i = 0; i < 256; i++ ) { - //Save them in reverse - ExpTable[i] = (int)( 0.5 + ( pow(2.0, ( 255 - i) * ( 1.0 /256 ) )-1) * 1024 ); - ExpTable[i] += 1024; //or remove the -1 oh well :) - //Preshift to the left once so the final volume can shift to the right - ExpTable[i] *= 2; - } -#endif -#if ( DBOPL_WAVE == WAVE_HANDLER ) - //Add 0.5 for the trunc rounding of the integer cast - //Do a PI sinetable instead of the original 0.5 PI - for ( int i = 0; i < 512; i++ ) { - SinTable[i] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 ); - } -#endif -#if ( DBOPL_WAVE == WAVE_TABLEMUL ) - //Multiplication based tables - for ( int i = 0; i < 384; i++ ) { - int s = i * 8; - //TODO maybe keep some of the precision errors of the original table? - double val = ( 0.5 + ( pow(2.0, -1.0 + ( 255 - s) * ( 1.0 /256 ) )) * ( 1 << MUL_SH )); - MulTable[i] = (Bit16u)(val); - } - - //Sine Wave Base - for ( int i = 0; i < 512; i++ ) { - WaveTable[ 0x0200 + i ] = (Bit16s)(sin( (i + 0.5) * (PI / 512.0) ) * 4084); - WaveTable[ 0x0000 + i ] = -WaveTable[ 0x200 + i ]; - } - //Exponential wave - for ( int i = 0; i < 256; i++ ) { - WaveTable[ 0x700 + i ] = (Bit16s)( 0.5 + ( pow(2.0, -1.0 + ( 255 - i * 8) * ( 1.0 /256 ) ) ) * 4085 ); - WaveTable[ 0x6ff - i ] = -WaveTable[ 0x700 + i ]; - } -#endif -#if ( DBOPL_WAVE == WAVE_TABLELOG ) - //Sine Wave Base - for ( int i = 0; i < 512; i++ ) { - WaveTable[ 0x0200 + i ] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 ); - WaveTable[ 0x0000 + i ] = ((Bit16s)0x8000) | WaveTable[ 0x200 + i]; - } - //Exponential wave - for ( int i = 0; i < 256; i++ ) { - WaveTable[ 0x700 + i ] = i * 8; - WaveTable[ 0x6ff - i ] = ((Bit16s)0x8000) | i * 8; - } -#endif - - // | |//\\|____|WAV7|//__|/\ |____|/\/\| - // |\\//| | |WAV7| | \/| | | - // |06 |0126|27 |7 |3 |4 |4 5 |5 | - -#if (( DBOPL_WAVE == WAVE_TABLELOG ) || ( DBOPL_WAVE == WAVE_TABLEMUL )) - for ( int i = 0; i < 256; i++ ) { - //Fill silence gaps - WaveTable[ 0x400 + i ] = WaveTable[0]; - WaveTable[ 0x500 + i ] = WaveTable[0]; - WaveTable[ 0x900 + i ] = WaveTable[0]; - WaveTable[ 0xc00 + i ] = WaveTable[0]; - WaveTable[ 0xd00 + i ] = WaveTable[0]; - //Replicate sines in other pieces - WaveTable[ 0x800 + i ] = WaveTable[ 0x200 + i ]; - //double speed sines - WaveTable[ 0xa00 + i ] = WaveTable[ 0x200 + i * 2 ]; - WaveTable[ 0xb00 + i ] = WaveTable[ 0x000 + i * 2 ]; - WaveTable[ 0xe00 + i ] = WaveTable[ 0x200 + i * 2 ]; - WaveTable[ 0xf00 + i ] = WaveTable[ 0x200 + i * 2 ]; - } -#endif - - //Create the ksl table - for ( int oct = 0; oct < 8; oct++ ) { - int base = oct * 8; - for ( int i = 0; i < 16; i++ ) { - int val = base - KslCreateTable[i]; - if ( val < 0 ) - val = 0; - //*4 for the final range to match attenuation range - KslTable[ oct * 16 + i ] = val * 4; - } - } - //Create the Tremolo table, just increase and decrease a triangle wave - for ( Bit8u i = 0; i < TREMOLO_TABLE / 2; i++ ) { - Bit8u val = i << ENV_EXTRA; - TremoloTable[i] = val; - TremoloTable[TREMOLO_TABLE - 1 - i] = val; - } - //Create a table with offsets of the channels from the start of the chip - DBOPL::Chip* chip = 0; - for ( Bitu i = 0; i < 32; i++ ) { - Bitu index = i & 0xf; - if ( index >= 9 ) { - ChanOffsetTable[i] = 0; - continue; - } - //Make sure the four op channels follow eachother - if ( index < 6 ) { - index = (index % 3) * 2 + ( index / 3 ); - } - //Add back the bits for highest ones - if ( i >= 16 ) - index += 9; - Bitu blah = reinterpret_cast( &(chip->chan[ index ]) ); - ChanOffsetTable[i] = blah; - } - //Same for operators - for ( Bitu i = 0; i < 64; i++ ) { - if ( i % 8 >= 6 || ( (i / 8) % 4 == 3 ) ) { - OpOffsetTable[i] = 0; - continue; - } - Bitu chNum = (i / 8) * 3 + (i % 8) % 3; - //Make sure we use 16 and up for the 2nd range to match the chanoffset gap - if ( chNum >= 12 ) - chNum += 16 - 12; - Bitu opNum = ( i % 8 ) / 3; - DBOPL::Channel* chan = 0; - Bitu blah = reinterpret_cast( &(chan->op[opNum]) ); - OpOffsetTable[i] = ChanOffsetTable[ chNum ] + blah; - } -#if 0 - //Stupid checks if table's are correct - for ( Bitu i = 0; i < 18; i++ ) { - Bit32u find = (Bit16u)( &(chip->chan[ i ]) ); - for ( Bitu c = 0; c < 32; c++ ) { - if ( ChanOffsetTable[c] == find ) { - find = 0; - break; - } - } - if ( find ) { - find = find; - } - } - for ( Bitu i = 0; i < 36; i++ ) { - Bit32u find = (Bit16u)( &(chip->chan[ i / 2 ].op[i % 2]) ); - for ( Bitu c = 0; c < 64; c++ ) { - if ( OpOffsetTable[c] == find ) { - find = 0; - break; - } - } - if ( find ) { - find = find; - } - } -#endif -} - -} //Namespace DBOPL -} // End of namespace DOSBox -} // End of namespace OPL - -#endif // !DISABLE_DOSBOX_OPL diff --git a/sound/softsynth/opl/dbopl.h b/sound/softsynth/opl/dbopl.h deleted file mode 100644 index 87d1045fab..0000000000 --- a/sound/softsynth/opl/dbopl.h +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2002-2010 The DOSBox Team - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -// Last synch with DOSBox SVN trunk r3556 - -#ifndef SOUND_SOFTSYNTH_OPL_DBOPL_H -#define SOUND_SOFTSYNTH_OPL_DBOPL_H - -#include "common/scummsys.h" - -#ifndef DISABLE_DOSBOX_OPL - -namespace OPL { -namespace DOSBox { - -//Use 8 handlers based on a small logatirmic wavetabe and an exponential table for volume -#define WAVE_HANDLER 10 -//Use a logarithmic wavetable with an exponential table for volume -#define WAVE_TABLELOG 11 -//Use a linear wavetable with a multiply table for volume -#define WAVE_TABLEMUL 12 - -//Select the type of wave generator routine -#define DBOPL_WAVE WAVE_TABLEMUL - -namespace DBOPL { - -// Type aliases for the DBOPL code -typedef int Bits; -typedef uint Bitu; - -typedef int8 Bit8s; -typedef uint8 Bit8u; - -typedef int16 Bit16s; -typedef uint16 Bit16u; - -typedef int32 Bit32s; -typedef uint32 Bit32u; - -#define DB_FASTCALL -#define GCC_UNLIKELY(x) (x) -#define INLINE inline -// ------------------------------- - -struct Chip; -struct Operator; -struct Channel; - -#if (DBOPL_WAVE == WAVE_HANDLER) -typedef Bits ( DB_FASTCALL *WaveHandler) ( Bitu i, Bitu volume ); -#endif - -typedef Bits ( DBOPL::Operator::*VolumeHandler) ( ); -typedef Channel* ( DBOPL::Channel::*SynthHandler) ( Chip* chip, Bit32u samples, Bit32s* output ); - -//Different synth modes that can generate blocks of data -typedef enum { - sm2AM, - sm2FM, - sm3AM, - sm3FM, - sm4Start, - sm3FMFM, - sm3AMFM, - sm3FMAM, - sm3AMAM, - sm6Start, - sm2Percussion, - sm3Percussion -} SynthMode; - -//Shifts for the values contained in chandata variable -enum { - SHIFT_KSLBASE = 16, - SHIFT_KEYCODE = 24 -}; - -struct Operator { -public: - //Masks for operator 20 values - enum { - MASK_KSR = 0x10, - MASK_SUSTAIN = 0x20, - MASK_VIBRATO = 0x40, - MASK_TREMOLO = 0x80 - }; - - typedef enum { - OFF, - RELEASE, - SUSTAIN, - DECAY, - ATTACK - } State; - - VolumeHandler volHandler; - -#if (DBOPL_WAVE == WAVE_HANDLER) - WaveHandler waveHandler; //Routine that generate a wave -#else - Bit16s* waveBase; - Bit32u waveMask; - Bit32u waveStart; -#endif - Bit32u waveIndex; //WAVE_BITS shifted counter of the frequency index - Bit32u waveAdd; //The base frequency without vibrato - Bit32u waveCurrent; //waveAdd + vibratao - - Bit32u chanData; //Frequency/octave and derived data coming from whatever channel controls this - Bit32u freqMul; //Scale channel frequency with this, TODO maybe remove? - Bit32u vibrato; //Scaled up vibrato strength - Bit32s sustainLevel; //When stopping at sustain level stop here - Bit32s totalLevel; //totalLevel is added to every generated volume - Bit32u currentLevel; //totalLevel + tremolo - Bit32s volume; //The currently active volume - - Bit32u attackAdd; //Timers for the different states of the envelope - Bit32u decayAdd; - Bit32u releaseAdd; - Bit32u rateIndex; //Current position of the evenlope - - Bit8u rateZero; //Bits for the different states of the envelope having no changes - Bit8u keyOn; //Bitmask of different values that can generate keyon - //Registers, also used to check for changes - Bit8u reg20, reg40, reg60, reg80, regE0; - //Active part of the envelope we're in - Bit8u state; - //0xff when tremolo is enabled - Bit8u tremoloMask; - //Strength of the vibrato - Bit8u vibStrength; - //Keep track of the calculated KSR so we can check for changes - Bit8u ksr; -private: - void SetState( Bit8u s ); - void UpdateAttack( const Chip* chip ); - void UpdateRelease( const Chip* chip ); - void UpdateDecay( const Chip* chip ); -public: - void UpdateAttenuation(); - void UpdateRates( const Chip* chip ); - void UpdateFrequency( ); - - void Write20( const Chip* chip, Bit8u val ); - void Write40( const Chip* chip, Bit8u val ); - void Write60( const Chip* chip, Bit8u val ); - void Write80( const Chip* chip, Bit8u val ); - void WriteE0( const Chip* chip, Bit8u val ); - - bool Silent() const; - void Prepare( const Chip* chip ); - - void KeyOn( Bit8u mask); - void KeyOff( Bit8u mask); - - template< State state> - Bits TemplateVolume( ); - - Bit32s RateForward( Bit32u add ); - Bitu ForwardWave(); - Bitu ForwardVolume(); - - Bits GetSample( Bits modulation ); - Bits GetWave( Bitu index, Bitu vol ); -public: - Operator(); -}; - -struct Channel { - Operator op[2]; - inline Operator* Op( Bitu index ) { - return &( ( this + (index >> 1) )->op[ index & 1 ]); - } - SynthHandler synthHandler; - Bit32u chanData; //Frequency/octave and derived values - Bit32s old[2]; //Old data for feedback - - Bit8u feedback; //Feedback shift - Bit8u regB0; //Register values to check for changes - Bit8u regC0; - //This should correspond with reg104, bit 6 indicates a Percussion channel, bit 7 indicates a silent channel - Bit8u fourMask; - Bit8s maskLeft; //Sign extended values for both channel's panning - Bit8s maskRight; - - //Forward the channel data to the operators of the channel - void SetChanData( const Chip* chip, Bit32u data ); - //Change in the chandata, check for new values and if we have to forward to operators - void UpdateFrequency( const Chip* chip, Bit8u fourOp ); - void WriteA0( const Chip* chip, Bit8u val ); - void WriteB0( const Chip* chip, Bit8u val ); - void WriteC0( const Chip* chip, Bit8u val ); - void ResetC0( const Chip* chip ); - - //call this for the first channel - template< bool opl3Mode > - void GeneratePercussion( Chip* chip, Bit32s* output ); - - //Generate blocks of data in specific modes - template - Channel* BlockTemplate( Chip* chip, Bit32u samples, Bit32s* output ); - Channel(); -}; - -struct Chip { - //This is used as the base counter for vibrato and tremolo - Bit32u lfoCounter; - Bit32u lfoAdd; - - - Bit32u noiseCounter; - Bit32u noiseAdd; - Bit32u noiseValue; - - //Frequency scales for the different multiplications - Bit32u freqMul[16]; - //Rates for decay and release for rate of this chip - Bit32u linearRates[76]; - //Best match attack rates for the rate of this chip - Bit32u attackRates[76]; - - //18 channels with 2 operators each - Channel chan[18]; - - Bit8u reg104; - Bit8u reg08; - Bit8u reg04; - Bit8u regBD; - Bit8u vibratoIndex; - Bit8u tremoloIndex; - Bit8s vibratoSign; - Bit8u vibratoShift; - Bit8u tremoloValue; - Bit8u vibratoStrength; - Bit8u tremoloStrength; - //Mask for allowed wave forms - Bit8u waveFormMask; - //0 or -1 when enabled - Bit8s opl3Active; - - //Return the maximum amount of samples before and LFO change - Bit32u ForwardLFO( Bit32u samples ); - Bit32u ForwardNoise(); - - void WriteBD( Bit8u val ); - void WriteReg(Bit32u reg, Bit8u val ); - - Bit32u WriteAddr( Bit32u port, Bit8u val ); - - void GenerateBlock2( Bitu samples, Bit32s* output ); - void GenerateBlock3( Bitu samples, Bit32s* output ); - - void Generate( Bit32u samples ); - void Setup( Bit32u r ); - - Chip(); -}; - -void InitTables(); - -} //Namespace -} // End of namespace DOSBox -} // End of namespace OPL - -#endif // !DISABLE_DOSBOX_OPL - -#endif diff --git a/sound/softsynth/opl/dosbox.cpp b/sound/softsynth/opl/dosbox.cpp deleted file mode 100644 index 29993ce3d8..0000000000 --- a/sound/softsynth/opl/dosbox.cpp +++ /dev/null @@ -1,335 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -/* - * Based on AdLib emulation code of DOSBox - * Copyright (C) 2002-2009 The DOSBox Team - * Licensed under GPLv2+ - * http://www.dosbox.com - */ - -#ifndef DISABLE_DOSBOX_OPL - -#include "dosbox.h" -#include "dbopl.h" - -#include "common/system.h" -#include "common/scummsys.h" - -#include -#include - -namespace OPL { -namespace DOSBox { - -Timer::Timer() { - masked = false; - overflow = false; - enabled = false; - counter = 0; - delay = 0; -} - -void Timer::update(double time) { - if (!enabled || !delay) - return; - double deltaStart = time - startTime; - // Only set the overflow flag when not masked - if (deltaStart >= 0 && !masked) - overflow = 1; -} - -void Timer::reset(double time) { - overflow = false; - if (!delay || !enabled) - return; - double delta = (time - startTime); - double rem = fmod(delta, delay); - double next = delay - rem; - startTime = time + next; -} - -void Timer::stop() { - enabled = false; -} - -void Timer::start(double time, int scale) { - //Don't enable again - if (enabled) - return; - enabled = true; - delay = 0.001 * (256 - counter) * scale; - startTime = time + delay; -} - -bool Chip::write(uint32 reg, uint8 val) { - switch (reg) { - case 0x02: - timer[0].counter = val; - return true; - case 0x03: - timer[1].counter = val; - return true; - case 0x04: - double time = g_system->getMillis() / 1000.0; - - if (val & 0x80) { - timer[0].reset(time); - timer[1].reset(time); - } else { - timer[0].update(time); - timer[1].update(time); - - if (val & 0x1) - timer[0].start(time, 80); - else - timer[0].stop(); - - timer[0].masked = (val & 0x40) > 0; - - if (timer[0].masked) - timer[0].overflow = false; - - if (val & 0x2) - timer[1].start(time, 320); - else - timer[1].stop(); - - timer[1].masked = (val & 0x20) > 0; - - if (timer[1].masked) - timer[1].overflow = false; - } - return true; - } - return false; -} - -uint8 Chip::read() { - double time = g_system->getMillis() / 1000.0; - - timer[0].update(time); - timer[1].update(time); - - uint8 ret = 0; - // Overflow won't be set if a channel is masked - if (timer[0].overflow) { - ret |= 0x40; - ret |= 0x80; - } - if (timer[1].overflow) { - ret |= 0x20; - ret |= 0x80; - } - return ret; -} - -OPL::OPL(Config::OplType type) : _type(type), _rate(0), _emulator(0) { -} - -OPL::~OPL() { - free(); -} - -void OPL::free() { - delete _emulator; - _emulator = 0; -} - -bool OPL::init(int rate) { - free(); - - memset(&_reg, 0, sizeof(_reg)); - memset(_chip, 0, sizeof(_chip)); - - _emulator = new DBOPL::Chip(); - if (!_emulator) - return false; - - DBOPL::InitTables(); - _emulator->Setup(rate); - - if (_type == Config::kDualOpl2) { - // Setup opl3 mode in the hander - _emulator->WriteReg(0x105, 1); - } - - _rate = rate; - return true; -} - -void OPL::reset() { - init(_rate); -} - -void OPL::write(int port, int val) { - if (port&1) { - switch (_type) { - case Config::kOpl2: - case Config::kOpl3: - if (!_chip[0].write(_reg.normal, val)) - _emulator->WriteReg(_reg.normal, val); - break; - case Config::kDualOpl2: - // Not a 0x??8 port, then write to a specific port - if (!(port & 0x8)) { - byte index = (port & 2) >> 1; - dualWrite(index, _reg.dual[index], val); - } else { - //Write to both ports - dualWrite(0, _reg.dual[0], val); - dualWrite(1, _reg.dual[1], val); - } - break; - } - } else { - // Ask the handler to write the address - // Make sure to clip them in the right range - switch (_type) { - case Config::kOpl2: - _reg.normal = _emulator->WriteAddr(port, val) & 0xff; - break; - case Config::kOpl3: - _reg.normal = _emulator->WriteAddr(port, val) & 0x1ff; - break; - case Config::kDualOpl2: - // Not a 0x?88 port, when write to a specific side - if (!(port & 0x8)) { - byte index = (port & 2) >> 1; - _reg.dual[index] = val & 0xff; - } else { - _reg.dual[0] = val & 0xff; - _reg.dual[1] = val & 0xff; - } - break; - } - } -} - -byte OPL::read(int port) { - switch (_type) { - case Config::kOpl2: - if (!(port & 1)) - //Make sure the low bits are 6 on opl2 - return _chip[0].read() | 0x6; - break; - case Config::kOpl3: - if (!(port & 1)) - return _chip[0].read(); - break; - case Config::kDualOpl2: - // Only return for the lower ports - if (port & 1) - return 0xff; - // Make sure the low bits are 6 on opl2 - return _chip[(port >> 1) & 1].read() | 0x6; - } - return 0; -} - -void OPL::writeReg(int r, int v) { - byte tempReg = 0; - switch (_type) { - case Config::kOpl2: - case Config::kDualOpl2: - case Config::kOpl3: - // We can't use _handler->writeReg here directly, since it would miss timer changes. - - // Backup old setup register - tempReg = _reg.normal; - - // We need to set the register we want to write to via port 0x388 - write(0x388, r); - // Do the real writing to the register - write(0x389, v); - // Restore the old register - write(0x388, tempReg); - break; - }; -} - -void OPL::dualWrite(uint8 index, uint8 reg, uint8 val) { - // Make sure you don't use opl3 features - // Don't allow write to disable opl3 - if (reg == 5) - return; - - // Only allow 4 waveforms - if (reg >= 0xE0 && reg <= 0xE8) - val &= 3; - - // Write to the timer? - if (_chip[index].write(reg, val)) - return; - - // Enabling panning - if (reg >= 0xC0 && reg <= 0xC8) { - val &= 15; - val |= index ? 0xA0 : 0x50; - } - - uint32 fullReg = reg + (index ? 0x100 : 0); - _emulator->WriteReg(fullReg, val); -} - -void OPL::readBuffer(int16 *buffer, int length) { - // For stereo OPL cards, we divide the sample count by 2, - // to match stereo AudioStream behavior. - if (_type != Config::kOpl2) - length >>= 1; - - const uint bufferLength = 512; - int32 tempBuffer[bufferLength * 2]; - - if (_emulator->opl3Active) { - while (length > 0) { - const uint readSamples = MIN(length, bufferLength); - - _emulator->GenerateBlock3(readSamples, tempBuffer); - - for (uint i = 0; i < (readSamples << 1); ++i) - buffer[i] = tempBuffer[i]; - - buffer += (readSamples << 1); - length -= readSamples; - } - } else { - while (length > 0) { - const uint readSamples = MIN(length, bufferLength << 1); - - _emulator->GenerateBlock2(readSamples, tempBuffer); - - for (uint i = 0; i < readSamples; ++i) - buffer[i] = tempBuffer[i]; - - buffer += readSamples; - length -= readSamples; - } - } -} - -} // End of namespace DOSBox -} // End of namespace OPL - -#endif // !DISABLE_DOSBOX_ADLIB diff --git a/sound/softsynth/opl/dosbox.h b/sound/softsynth/opl/dosbox.h deleted file mode 100644 index 958310611d..0000000000 --- a/sound/softsynth/opl/dosbox.h +++ /dev/null @@ -1,110 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -/* - * Based on OPL emulation code of DOSBox - * Copyright (C) 2002-2009 The DOSBox Team - * Licensed under GPLv2+ - * http://www.dosbox.com - */ - -#ifndef SOUND_SOFTSYNTH_OPL_DOSBOX_H -#define SOUND_SOFTSYNTH_OPL_DOSBOX_H - -#ifndef DISABLE_DOSBOX_OPL - -#include "sound/fmopl.h" - -namespace OPL { -namespace DOSBox { - -struct Timer { - double startTime; - double delay; - bool enabled, overflow, masked; - uint8 counter; - - Timer(); - - //Call update before making any further changes - void update(double time); - - //On a reset make sure the start is in sync with the next cycle - void reset(double time); - - void stop(); - - void start(double time, int scale); -}; - -struct Chip { - //Last selected register - Timer timer[2]; - //Check for it being a write to the timer - bool write(uint32 addr, uint8 val); - //Read the current timer state, will use current double - uint8 read(); -}; - -namespace DBOPL { -struct Chip; -} // end of namespace DBOPL - -class OPL : public ::OPL::OPL { -private: - Config::OplType _type; - uint _rate; - - DBOPL::Chip *_emulator; - Chip _chip[2]; - union { - uint16 normal; - uint8 dual[2]; - } _reg; - - void free(); - void dualWrite(uint8 index, uint8 reg, uint8 val); -public: - OPL(Config::OplType type); - ~OPL(); - - bool init(int rate); - void reset(); - - void write(int a, int v); - byte read(int a); - - void writeReg(int r, int v); - - void readBuffer(int16 *buffer, int length); - bool isStereo() const { return _type != Config::kOpl2; } -}; - -} // End of namespace DOSBox -} // End of namespace OPL - -#endif // !DISABLE_DOSBOX_OPL - -#endif - diff --git a/sound/softsynth/opl/mame.cpp b/sound/softsynth/opl/mame.cpp deleted file mode 100644 index c875080e8f..0000000000 --- a/sound/softsynth/opl/mame.cpp +++ /dev/null @@ -1,1234 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - * LGPL licensed version of MAMEs fmopl (V0.37a modified) by - * Tatsuyuki Satoh. Included from LGPL'ed AdPlug. - */ - -#include -#include -#include -#include -#include - -#include "mame.h" - -#if defined (_WIN32_WCE) || defined (__SYMBIAN32__) || defined(__GP32__) || defined(GP2X) || defined (__MAEMO__) || defined(__DS__) || defined (__MINT__) || defined(__N64__) -#include "common/config-manager.h" -#endif - -#if defined(__DS__) -#include "dsmain.h" -#endif - -namespace OPL { -namespace MAME { - -OPL::~OPL() { - MAME::OPLDestroy(_opl); - _opl = 0; -} - -bool OPL::init(int rate) { - if (_opl) - MAME::OPLDestroy(_opl); - - _opl = MAME::makeAdLibOPL(rate); - return (_opl != 0); -} - -void OPL::reset() { - MAME::OPLResetChip(_opl); -} - -void OPL::write(int a, int v) { - MAME::OPLWrite(_opl, a, v); -} - -byte OPL::read(int a) { - return MAME::OPLRead(_opl, a); -} - -void OPL::writeReg(int r, int v) { - MAME::OPLWriteReg(_opl, r, v); -} - -void OPL::readBuffer(int16 *buffer, int length) { - MAME::YM3812UpdateOne(_opl, buffer, length); -} - -/* -------------------- preliminary define section --------------------- */ -/* attack/decay rate time rate */ -#define OPL_ARRATE 141280 /* RATE 4 = 2826.24ms @ 3.6MHz */ -#define OPL_DRRATE 1956000 /* RATE 4 = 39280.64ms @ 3.6MHz */ - -#define FREQ_BITS 24 /* frequency turn */ - -/* counter bits = 20 , octerve 7 */ -#define FREQ_RATE (1<<(FREQ_BITS-20)) -#define TL_BITS (FREQ_BITS+2) - -/* final output shift , limit minimum and maximum */ -#define OPL_OUTSB (TL_BITS+3-16) /* OPL output final shift 16bit */ -#define OPL_MAXOUT (0x7fff<status |= flag; - if (!(OPL->status & 0x80)) { - if (OPL->status & OPL->statusmask) { /* IRQ on */ - OPL->status |= 0x80; - /* callback user interrupt handler (IRQ is OFF to ON) */ - if (OPL->IRQHandler) - (OPL->IRQHandler)(OPL->IRQParam,1); - } - } -} - -/* status reset and IRQ handling */ -inline void OPL_STATUS_RESET(FM_OPL *OPL, int flag) { - /* reset status flag */ - OPL->status &= ~flag; - if ((OPL->status & 0x80)) { - if (!(OPL->status & OPL->statusmask)) { - OPL->status &= 0x7f; - /* callback user interrupt handler (IRQ is ON to OFF) */ - if (OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); - } - } -} - -/* IRQ mask set */ -inline void OPL_STATUSMASK_SET(FM_OPL *OPL, int flag) { - OPL->statusmask = flag; - /* IRQ handling check */ - OPL_STATUS_SET(OPL,0); - OPL_STATUS_RESET(OPL,0); -} - -/* ----- key on ----- */ -inline void OPL_KEYON(OPL_SLOT *SLOT) { - /* sin wave restart */ - SLOT->Cnt = 0; - /* set attack */ - SLOT->evm = ENV_MOD_AR; - SLOT->evs = SLOT->evsa; - SLOT->evc = EG_AST; - SLOT->eve = EG_AED; -} - -/* ----- key off ----- */ -inline void OPL_KEYOFF(OPL_SLOT *SLOT) { - if (SLOT->evm > ENV_MOD_RR) { - /* set envelope counter from envleope output */ - - // WORKAROUND: The Kyra engine does something very strange when - // starting a new song. For each channel: - // - // * The release rate is set to "fastest". - // * Any note is keyed off. - // * A very low-frequency note is keyed on. - // - // Usually, what happens next is that the real notes is keyed - // on immediately, in which case there's no problem. - // - // However, if the note is again keyed off (because the channel - // begins on a rest rather than a note), the envelope counter - // was moved from the very lowest point on the attack curve to - // the very highest point on the release curve. - // - // Again, this might not be a problem, if the release rate is - // still set to "fastest". But in many cases, it had already - // been increased. And, possibly because of inaccuracies in the - // envelope generator, that would cause the note to "fade out" - // for quite a long time. - // - // What we really need is a way to find the correct starting - // point for the envelope counter, and that may be what the - // commented-out line below is meant to do. For now, simply - // handle the pathological case. - - if (SLOT->evm == ENV_MOD_AR && SLOT->evc == EG_AST) - SLOT->evc = EG_DED; - else if (!(SLOT->evc & EG_DST)) - //SLOT->evc = (ENV_CURVE[SLOT->evc>>ENV_BITS]<evc = EG_DST; - SLOT->eve = EG_DED; - SLOT->evs = SLOT->evsr; - SLOT->evm = ENV_MOD_RR; - } -} - -/* ---------- calcrate Envelope Generator & Phase Generator ---------- */ - -/* return : envelope output */ -inline uint OPL_CALC_SLOT(OPL_SLOT *SLOT) { - /* calcrate envelope generator */ - if ((SLOT->evc += SLOT->evs) >= SLOT->eve) { - switch (SLOT->evm) { - case ENV_MOD_AR: /* ATTACK -> DECAY1 */ - /* next DR */ - SLOT->evm = ENV_MOD_DR; - SLOT->evc = EG_DST; - SLOT->eve = SLOT->SL; - SLOT->evs = SLOT->evsd; - break; - case ENV_MOD_DR: /* DECAY -> SL or RR */ - SLOT->evc = SLOT->SL; - SLOT->eve = EG_DED; - if (SLOT->eg_typ) { - SLOT->evs = 0; - } else { - SLOT->evm = ENV_MOD_RR; - SLOT->evs = SLOT->evsr; - } - break; - case ENV_MOD_RR: /* RR -> OFF */ - SLOT->evc = EG_OFF; - SLOT->eve = EG_OFF + 1; - SLOT->evs = 0; - break; - } - } - /* calcrate envelope */ - return SLOT->TLL + ENV_CURVE[SLOT->evc>>ENV_BITS] + (SLOT->ams ? ams : 0); -} - -/* set algorythm connection */ -static void set_algorythm(OPL_CH *CH) { - int *carrier = &outd[0]; - CH->connect1 = CH->CON ? carrier : &feedback2; - CH->connect2 = carrier; -} - -/* ---------- frequency counter for operater update ---------- */ -inline void CALC_FCSLOT(OPL_CH *CH, OPL_SLOT *SLOT) { - int ksr; - - /* frequency step counter */ - SLOT->Incr = CH->fc * SLOT->mul; - ksr = CH->kcode >> SLOT->KSR; - - if (SLOT->ksr != ksr) { - SLOT->ksr = ksr; - /* attack , decay rate recalcration */ - SLOT->evsa = SLOT->AR[ksr]; - SLOT->evsd = SLOT->DR[ksr]; - SLOT->evsr = SLOT->RR[ksr]; - } - SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); -} - -/* set multi,am,vib,EG-TYP,KSR,mul */ -inline void set_mul(FM_OPL *OPL, int slot, int v) { - OPL_CH *CH = &OPL->P_CH[slot>>1]; - OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; - - SLOT->mul = MUL_TABLE[v & 0x0f]; - SLOT->KSR = (v & 0x10) ? 0 : 2; - SLOT->eg_typ = (v & 0x20) >> 5; - SLOT->vib = (v & 0x40); - SLOT->ams = (v & 0x80); - CALC_FCSLOT(CH, SLOT); -} - -/* set ksl & tl */ -inline void set_ksl_tl(FM_OPL *OPL, int slot, int v) { - OPL_CH *CH = &OPL->P_CH[slot>>1]; - OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; - int ksl = v >> 6; /* 0 / 1.5 / 3 / 6 db/OCT */ - - SLOT->ksl = ksl ? 3-ksl : 31; - SLOT->TL = (int)((v & 0x3f) * (0.75 / EG_STEP)); /* 0.75db step */ - - if (!(OPL->mode & 0x80)) { /* not CSM latch total level */ - SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl); - } -} - -/* set attack rate & decay rate */ -inline void set_ar_dr(FM_OPL *OPL, int slot, int v) { - OPL_CH *CH = &OPL->P_CH[slot>>1]; - OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; - int ar = v >> 4; - int dr = v & 0x0f; - - SLOT->AR = ar ? &OPL->AR_TABLE[ar << 2] : RATE_0; - SLOT->evsa = SLOT->AR[SLOT->ksr]; - if (SLOT->evm == ENV_MOD_AR) - SLOT->evs = SLOT->evsa; - - SLOT->DR = dr ? &OPL->DR_TABLE[dr<<2] : RATE_0; - SLOT->evsd = SLOT->DR[SLOT->ksr]; - if (SLOT->evm == ENV_MOD_DR) - SLOT->evs = SLOT->evsd; -} - -/* set sustain level & release rate */ -inline void set_sl_rr(FM_OPL *OPL, int slot, int v) { - OPL_CH *CH = &OPL->P_CH[slot>>1]; - OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; - int sl = v >> 4; - int rr = v & 0x0f; - - SLOT->SL = SL_TABLE[sl]; - if (SLOT->evm == ENV_MOD_DR) - SLOT->eve = SLOT->SL; - SLOT->RR = &OPL->DR_TABLE[rr<<2]; - SLOT->evsr = SLOT->RR[SLOT->ksr]; - if (SLOT->evm == ENV_MOD_RR) - SLOT->evs = SLOT->evsr; -} - -/* operator output calcrator */ - -#define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt + con)>>(24-SIN_ENT_SHIFT)) & (SIN_ENT-1)][env] -/* ---------- calcrate one of channel ---------- */ -inline void OPL_CALC_CH(OPL_CH *CH) { - uint env_out; - OPL_SLOT *SLOT; - - feedback2 = 0; - /* SLOT 1 */ - SLOT = &CH->SLOT[SLOT1]; - env_out=OPL_CALC_SLOT(SLOT); - if (env_out < (uint)(EG_ENT - 1)) { - /* PG */ - if (SLOT->vib) - SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; - else - SLOT->Cnt += SLOT->Incr; - /* connection */ - if (CH->FB) { - int feedback1 = (CH->op1_out[0] + CH->op1_out[1]) >> CH->FB; - CH->op1_out[1] = CH->op1_out[0]; - *CH->connect1 += CH->op1_out[0] = OP_OUT(SLOT, env_out, feedback1); - } else { - *CH->connect1 += OP_OUT(SLOT, env_out, 0); - } - } else { - CH->op1_out[1] = CH->op1_out[0]; - CH->op1_out[0] = 0; - } - /* SLOT 2 */ - SLOT = &CH->SLOT[SLOT2]; - env_out=OPL_CALC_SLOT(SLOT); - if (env_out < (uint)(EG_ENT - 1)) { - /* PG */ - if (SLOT->vib) - SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; - else - SLOT->Cnt += SLOT->Incr; - /* connection */ - outd[0] += OP_OUT(SLOT, env_out, feedback2); - } -} - -/* ---------- calcrate rythm block ---------- */ -#define WHITE_NOISE_db 6.0 -inline void OPL_CALC_RH(FM_OPL *OPL, OPL_CH *CH) { - uint env_tam, env_sd, env_top, env_hh; - // This code used to do int(OPL->rnd.getRandomBit() * (WHITE_NOISE_db / EG_STEP)), - // but EG_STEP = 96.0/EG_ENT, and WHITE_NOISE_db=6.0. So, that's equivalent to - // int(OPL->rnd.getRandomBit() * EG_ENT/16). We know that EG_ENT is 4096, or 1024, - // or 128, so we can safely avoid any FP ops. - int whitenoise = OPL->rnd.getRandomBit() * (EG_ENT>>4); - - int tone8; - - OPL_SLOT *SLOT; - int env_out; - - /* BD : same as FM serial mode and output level is large */ - feedback2 = 0; - /* SLOT 1 */ - SLOT = &CH[6].SLOT[SLOT1]; - env_out = OPL_CALC_SLOT(SLOT); - if (env_out < EG_ENT-1) { - /* PG */ - if (SLOT->vib) - SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; - else - SLOT->Cnt += SLOT->Incr; - /* connection */ - if (CH[6].FB) { - int feedback1 = (CH[6].op1_out[0] + CH[6].op1_out[1]) >> CH[6].FB; - CH[6].op1_out[1] = CH[6].op1_out[0]; - feedback2 = CH[6].op1_out[0] = OP_OUT(SLOT, env_out, feedback1); - } - else { - feedback2 = OP_OUT(SLOT, env_out, 0); - } - } else { - feedback2 = 0; - CH[6].op1_out[1] = CH[6].op1_out[0]; - CH[6].op1_out[0] = 0; - } - /* SLOT 2 */ - SLOT = &CH[6].SLOT[SLOT2]; - env_out = OPL_CALC_SLOT(SLOT); - if (env_out < EG_ENT-1) { - /* PG */ - if (SLOT->vib) - SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; - else - SLOT->Cnt += SLOT->Incr; - /* connection */ - outd[0] += OP_OUT(SLOT, env_out, feedback2) * 2; - } - - // SD (17) = mul14[fnum7] + white noise - // TAM (15) = mul15[fnum8] - // TOP (18) = fnum6(mul18[fnum8]+whitenoise) - // HH (14) = fnum7(mul18[fnum8]+whitenoise) + white noise - env_sd = OPL_CALC_SLOT(SLOT7_2) + whitenoise; - env_tam =OPL_CALC_SLOT(SLOT8_1); - env_top = OPL_CALC_SLOT(SLOT8_2); - env_hh = OPL_CALC_SLOT(SLOT7_1) + whitenoise; - - /* PG */ - if (SLOT7_1->vib) - SLOT7_1->Cnt += (SLOT7_1->Incr * vib) >> (VIB_RATE_SHIFT-1); - else - SLOT7_1->Cnt += 2 * SLOT7_1->Incr; - if (SLOT7_2->vib) - SLOT7_2->Cnt += (CH[7].fc * vib) >> (VIB_RATE_SHIFT-3); - else - SLOT7_2->Cnt += (CH[7].fc * 8); - if (SLOT8_1->vib) - SLOT8_1->Cnt += (SLOT8_1->Incr * vib) >> VIB_RATE_SHIFT; - else - SLOT8_1->Cnt += SLOT8_1->Incr; - if (SLOT8_2->vib) - SLOT8_2->Cnt += ((CH[8].fc * 3) * vib) >> (VIB_RATE_SHIFT-4); - else - SLOT8_2->Cnt += (CH[8].fc * 48); - - tone8 = OP_OUT(SLOT8_2,whitenoise,0 ); - - /* SD */ - if (env_sd < (uint)(EG_ENT - 1)) - outd[0] += OP_OUT(SLOT7_1, env_sd, 0) * 8; - /* TAM */ - if (env_tam < (uint)(EG_ENT - 1)) - outd[0] += OP_OUT(SLOT8_1, env_tam, 0) * 2; - /* TOP-CY */ - if (env_top < (uint)(EG_ENT - 1)) - outd[0] += OP_OUT(SLOT7_2, env_top, tone8) * 2; - /* HH */ - if (env_hh < (uint)(EG_ENT-1)) - outd[0] += OP_OUT(SLOT7_2, env_hh, tone8) * 2; -} - -/* ----------- initialize time tabls ----------- */ -static void init_timetables(FM_OPL *OPL, int ARRATE, int DRRATE) { - int i; - double rate; - - /* make attack rate & decay rate tables */ - for (i = 0; i < 4; i++) - OPL->AR_TABLE[i] = OPL->DR_TABLE[i] = 0; - for (i = 4; i <= 60; i++) { - rate = OPL->freqbase; /* frequency rate */ - if (i < 60) - rate *= 1.0 + (i & 3) * 0.25; /* b0-1 : x1 , x1.25 , x1.5 , x1.75 */ - rate *= 1 << ((i >> 2) - 1); /* b2-5 : shift bit */ - rate *= (double)(EG_ENT << ENV_BITS); - OPL->AR_TABLE[i] = (int)(rate / ARRATE); - OPL->DR_TABLE[i] = (int)(rate / DRRATE); - } - for (i = 60; i < 76; i++) { - OPL->AR_TABLE[i] = EG_AED-1; - OPL->DR_TABLE[i] = OPL->DR_TABLE[60]; - } -} - -/* ---------- generic table initialize ---------- */ -static int OPLOpenTable(void) { - int s,t; - double rate; - int i,j; - double pom; - -#ifdef __DS__ - DS::fastRamReset(); - - TL_TABLE = (int *) DS::fastRamAlloc(TL_MAX * 2 * sizeof(int *)); - SIN_TABLE = (int **) DS::fastRamAlloc(SIN_ENT * 4 * sizeof(int *)); -#else - - /* allocate dynamic tables */ - if ((TL_TABLE = (int *)malloc(TL_MAX * 2 * sizeof(int))) == NULL) - return 0; - - if ((SIN_TABLE = (int **)malloc(SIN_ENT * 4 * sizeof(int *))) == NULL) { - free(TL_TABLE); - return 0; - } -#endif - - if ((AMS_TABLE = (int *)malloc(AMS_ENT * 2 * sizeof(int))) == NULL) { - free(TL_TABLE); - free(SIN_TABLE); - return 0; - } - - if ((VIB_TABLE = (int *)malloc(VIB_ENT * 2 * sizeof(int))) == NULL) { - free(TL_TABLE); - free(SIN_TABLE); - free(AMS_TABLE); - return 0; - } - /* make total level table */ - for (t = 0; t < EG_ENT - 1; t++) { - rate = ((1 << TL_BITS) - 1) / pow(10.0, EG_STEP * t / 20); /* dB -> voltage */ - TL_TABLE[ t] = (int)rate; - TL_TABLE[TL_MAX + t] = -TL_TABLE[t]; - } - /* fill volume off area */ - for (t = EG_ENT - 1; t < TL_MAX; t++) { - TL_TABLE[t] = TL_TABLE[TL_MAX + t] = 0; - } - - /* make sinwave table (total level offet) */ - /* degree 0 = degree 180 = off */ - SIN_TABLE[0] = SIN_TABLE[SIN_ENT /2 ] = &TL_TABLE[EG_ENT - 1]; - for (s = 1;s <= SIN_ENT / 4; s++) { - pom = sin(2 * PI * s / SIN_ENT); /* sin */ - pom = 20 * log10(1 / pom); /* decibel */ - j = int(pom / EG_STEP); /* TL_TABLE steps */ - - /* degree 0 - 90 , degree 180 - 90 : plus section */ - SIN_TABLE[ s] = SIN_TABLE[SIN_ENT / 2 - s] = &TL_TABLE[j]; - /* degree 180 - 270 , degree 360 - 270 : minus section */ - SIN_TABLE[SIN_ENT / 2 + s] = SIN_TABLE[SIN_ENT - s] = &TL_TABLE[TL_MAX + j]; - } - for (s = 0;s < SIN_ENT; s++) { - SIN_TABLE[SIN_ENT * 1 + s] = s < (SIN_ENT / 2) ? SIN_TABLE[s] : &TL_TABLE[EG_ENT]; - SIN_TABLE[SIN_ENT * 2 + s] = SIN_TABLE[s % (SIN_ENT / 2)]; - SIN_TABLE[SIN_ENT * 3 + s] = (s / (SIN_ENT / 4)) & 1 ? &TL_TABLE[EG_ENT] : SIN_TABLE[SIN_ENT * 2 + s]; - } - - - ENV_CURVE = (int *)malloc(sizeof(int) * (2*EG_ENT+1)); - - /* envelope counter -> envelope output table */ - for (i=0; i < EG_ENT; i++) { - /* ATTACK curve */ - pom = pow(((double)(EG_ENT - 1 - i) / EG_ENT), 8) * EG_ENT; - /* if (pom >= EG_ENT) pom = EG_ENT-1; */ - ENV_CURVE[i] = (int)pom; - /* DECAY ,RELEASE curve */ - ENV_CURVE[(EG_DST >> ENV_BITS) + i]= i; - } - /* off */ - ENV_CURVE[EG_OFF >> ENV_BITS]= EG_ENT - 1; - /* make LFO ams table */ - for (i=0; i < AMS_ENT; i++) { - pom = (1.0 + sin(2 * PI * i / AMS_ENT)) / 2; /* sin */ - AMS_TABLE[i] = (int)((1.0 / EG_STEP) * pom); /* 1dB */ - AMS_TABLE[AMS_ENT + i] = (int)((4.8 / EG_STEP) * pom); /* 4.8dB */ - } - /* make LFO vibrate table */ - for (i=0; i < VIB_ENT; i++) { - /* 100cent = 1seminote = 6% ?? */ - pom = (double)VIB_RATE * 0.06 * sin(2 * PI * i / VIB_ENT); /* +-100sect step */ - VIB_TABLE[i] = (int)(VIB_RATE + (pom * 0.07)); /* +- 7cent */ - VIB_TABLE[VIB_ENT + i] = (int)(VIB_RATE + (pom * 0.14)); /* +-14cent */ - } - return 1; -} - -static void OPLCloseTable(void) { - free(TL_TABLE); - free(SIN_TABLE); - free(AMS_TABLE); - free(VIB_TABLE); - free(ENV_CURVE); -} - -/* CSM Key Controll */ -inline void CSMKeyControll(OPL_CH *CH) { - OPL_SLOT *slot1 = &CH->SLOT[SLOT1]; - OPL_SLOT *slot2 = &CH->SLOT[SLOT2]; - /* all key off */ - OPL_KEYOFF(slot1); - OPL_KEYOFF(slot2); - /* total level latch */ - slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); - slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); - /* key on */ - CH->op1_out[0] = CH->op1_out[1] = 0; - OPL_KEYON(slot1); - OPL_KEYON(slot2); -} - -/* ---------- opl initialize ---------- */ -static void OPL_initalize(FM_OPL *OPL) { - int fn; - - /* frequency base */ - OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / OPL->rate) / 72 : 0; - /* Timer base time */ - OPL->TimerBase = 1.0/((double)OPL->clock / 72.0 ); - /* make time tables */ - init_timetables(OPL, OPL_ARRATE, OPL_DRRATE); - /* make fnumber -> increment counter table */ - for (fn=0; fn < 1024; fn++) { - OPL->FN_TABLE[fn] = (uint)(OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2); - } - /* LFO freq.table */ - OPL->amsIncr = (int)(OPL->rate ? (double)AMS_ENT * (1 << AMS_SHIFT) / OPL->rate * 3.7 * ((double)OPL->clock/3600000) : 0); - OPL->vibIncr = (int)(OPL->rate ? (double)VIB_ENT * (1 << VIB_SHIFT) / OPL->rate * 6.4 * ((double)OPL->clock/3600000) : 0); -} - -/* ---------- write a OPL registers ---------- */ -void OPLWriteReg(FM_OPL *OPL, int r, int v) { - OPL_CH *CH; - int slot; - uint block_fnum; - - switch (r & 0xe0) { - case 0x00: /* 00-1f:controll */ - switch (r & 0x1f) { - case 0x01: - /* wave selector enable */ - if (OPL->type&OPL_TYPE_WAVESEL) { - OPL->wavesel = v & 0x20; - if (!OPL->wavesel) { - /* preset compatible mode */ - int c; - for (c = 0; c < OPL->max_ch; c++) { - OPL->P_CH[c].SLOT[SLOT1].wavetable = &SIN_TABLE[0]; - OPL->P_CH[c].SLOT[SLOT2].wavetable = &SIN_TABLE[0]; - } - } - } - return; - case 0x02: /* Timer 1 */ - OPL->T[0] = (256-v) * 4; - break; - case 0x03: /* Timer 2 */ - OPL->T[1] = (256-v) * 16; - return; - case 0x04: /* IRQ clear / mask and Timer enable */ - if (v & 0x80) { /* IRQ flag clear */ - OPL_STATUS_RESET(OPL, 0x7f); - } else { /* set IRQ mask ,timer enable*/ - uint8 st1 = v & 1; - uint8 st2 = (v >> 1) & 1; - /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ - OPL_STATUS_RESET(OPL, v & 0x78); - OPL_STATUSMASK_SET(OPL,((~v) & 0x78) | 0x01); - /* timer 2 */ - if (OPL->st[1] != st2) { - double interval = st2 ? (double)OPL->T[1] * OPL->TimerBase : 0.0; - OPL->st[1] = st2; - if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 1, interval); - } - /* timer 1 */ - if (OPL->st[0] != st1) { - double interval = st1 ? (double)OPL->T[0] * OPL->TimerBase : 0.0; - OPL->st[0] = st1; - if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 0, interval); - } - } - return; - } - break; - case 0x20: /* am,vib,ksr,eg type,mul */ - slot = slot_array[r&0x1f]; - if (slot == -1) - return; - set_mul(OPL,slot,v); - return; - case 0x40: - slot = slot_array[r&0x1f]; - if (slot == -1) - return; - set_ksl_tl(OPL,slot,v); - return; - case 0x60: - slot = slot_array[r&0x1f]; - if (slot == -1) - return; - set_ar_dr(OPL,slot,v); - return; - case 0x80: - slot = slot_array[r&0x1f]; - if (slot == -1) - return; - set_sl_rr(OPL,slot,v); - return; - case 0xa0: - switch (r) { - case 0xbd: - /* amsep,vibdep,r,bd,sd,tom,tc,hh */ - { - uint8 rkey = OPL->rythm ^ v; - OPL->ams_table = &AMS_TABLE[v & 0x80 ? AMS_ENT : 0]; - OPL->vib_table = &VIB_TABLE[v & 0x40 ? VIB_ENT : 0]; - OPL->rythm = v & 0x3f; - if (OPL->rythm & 0x20) { - /* BD key on/off */ - if (rkey & 0x10) { - if (v & 0x10) { - OPL->P_CH[6].op1_out[0] = OPL->P_CH[6].op1_out[1] = 0; - OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT1]); - OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT2]); - } else { - OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1]); - OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2]); - } - } - /* SD key on/off */ - if (rkey & 0x08) { - if (v & 0x08) - OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT2]); - else - OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2]); - }/* TAM key on/off */ - if (rkey & 0x04) { - if (v & 0x04) - OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT1]); - else - OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1]); - } - /* TOP-CY key on/off */ - if (rkey & 0x02) { - if (v & 0x02) - OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT2]); - else - OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2]); - } - /* HH key on/off */ - if (rkey & 0x01) { - if (v & 0x01) - OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT1]); - else - OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1]); - } - } - } - return; - - default: - break; - } - /* keyon,block,fnum */ - if ((r & 0x0f) > 8) - return; - CH = &OPL->P_CH[r & 0x0f]; - if (!(r&0x10)) { /* a0-a8 */ - block_fnum = (CH->block_fnum & 0x1f00) | v; - } else { /* b0-b8 */ - int keyon = (v >> 5) & 1; - block_fnum = ((v & 0x1f) << 8) | (CH->block_fnum & 0xff); - if (CH->keyon != keyon) { - if ((CH->keyon=keyon)) { - CH->op1_out[0] = CH->op1_out[1] = 0; - OPL_KEYON(&CH->SLOT[SLOT1]); - OPL_KEYON(&CH->SLOT[SLOT2]); - } else { - OPL_KEYOFF(&CH->SLOT[SLOT1]); - OPL_KEYOFF(&CH->SLOT[SLOT2]); - } - } - } - /* update */ - if (CH->block_fnum != block_fnum) { - int blockRv = 7 - (block_fnum >> 10); - int fnum = block_fnum & 0x3ff; - CH->block_fnum = block_fnum; - CH->ksl_base = KSL_TABLE[block_fnum >> 6]; - CH->fc = OPL->FN_TABLE[fnum] >> blockRv; - CH->kcode = CH->block_fnum >> 9; - if ((OPL->mode & 0x40) && CH->block_fnum & 0x100) - CH->kcode |=1; - CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); - CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); - } - return; - case 0xc0: - /* FB,C */ - if ((r & 0x0f) > 8) - return; - CH = &OPL->P_CH[r&0x0f]; - { - int feedback = (v >> 1) & 7; - CH->FB = feedback ? (8 + 1) - feedback : 0; - CH->CON = v & 1; - set_algorythm(CH); - } - return; - case 0xe0: /* wave type */ - slot = slot_array[r & 0x1f]; - if (slot == -1) - return; - CH = &OPL->P_CH[slot>>1]; - if (OPL->wavesel) { - CH->SLOT[slot&1].wavetable = &SIN_TABLE[(v & 0x03) * SIN_ENT]; - } - return; - } -} - -/* lock/unlock for common table */ -static int OPL_LockTable(void) { - num_lock++; - if (num_lock>1) - return 0; - /* first time */ - cur_chip = NULL; - /* allocate total level table (128kb space) */ - if (!OPLOpenTable()) { - num_lock--; - return -1; - } - return 0; -} - -static void OPL_UnLockTable(void) { - if (num_lock) - num_lock--; - if (num_lock) - return; - /* last time */ - cur_chip = NULL; - OPLCloseTable(); -} - -/*******************************************************************************/ -/* YM3812 local section */ -/*******************************************************************************/ - -/* ---------- update one of chip ----------- */ -void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length) { - int i; - int data; - int16 *buf = buffer; - uint amsCnt = OPL->amsCnt; - uint vibCnt = OPL->vibCnt; - uint8 rythm = OPL->rythm & 0x20; - OPL_CH *CH, *R_CH; - - - if ((void *)OPL != cur_chip) { - cur_chip = (void *)OPL; - /* channel pointers */ - S_CH = OPL->P_CH; - E_CH = &S_CH[9]; - /* rythm slot */ - SLOT7_1 = &S_CH[7].SLOT[SLOT1]; - SLOT7_2 = &S_CH[7].SLOT[SLOT2]; - SLOT8_1 = &S_CH[8].SLOT[SLOT1]; - SLOT8_2 = &S_CH[8].SLOT[SLOT2]; - /* LFO state */ - amsIncr = OPL->amsIncr; - vibIncr = OPL->vibIncr; - ams_table = OPL->ams_table; - vib_table = OPL->vib_table; - } - R_CH = rythm ? &S_CH[6] : E_CH; - for (i = 0; i < length; i++) { - /* channel A channel B channel C */ - /* LFO */ - ams = ams_table[(amsCnt += amsIncr) >> AMS_SHIFT]; - vib = vib_table[(vibCnt += vibIncr) >> VIB_SHIFT]; - outd[0] = 0; - /* FM part */ - for (CH = S_CH; CH < R_CH; CH++) - OPL_CALC_CH(CH); - /* Rythn part */ - if (rythm) - OPL_CALC_RH(OPL, S_CH); - /* limit check */ - data = CLIP(outd[0], OPL_MINOUT, OPL_MAXOUT); - /* store to sound buffer */ - buf[i] = data >> OPL_OUTSB; - } - - OPL->amsCnt = amsCnt; - OPL->vibCnt = vibCnt; -} - -/* ---------- reset a chip ---------- */ -void OPLResetChip(FM_OPL *OPL) { - int c,s; - int i; - - /* reset chip */ - OPL->mode = 0; /* normal mode */ - OPL_STATUS_RESET(OPL, 0x7f); - /* reset with register write */ - OPLWriteReg(OPL, 0x01,0); /* wabesel disable */ - OPLWriteReg(OPL, 0x02,0); /* Timer1 */ - OPLWriteReg(OPL, 0x03,0); /* Timer2 */ - OPLWriteReg(OPL, 0x04,0); /* IRQ mask clear */ - for (i = 0xff; i >= 0x20; i--) - OPLWriteReg(OPL,i,0); - /* reset OPerator parameter */ - for (c = 0; c < OPL->max_ch; c++) { - OPL_CH *CH = &OPL->P_CH[c]; - /* OPL->P_CH[c].PAN = OPN_CENTER; */ - for (s = 0; s < 2; s++) { - /* wave table */ - CH->SLOT[s].wavetable = &SIN_TABLE[0]; - /* CH->SLOT[s].evm = ENV_MOD_RR; */ - CH->SLOT[s].evc = EG_OFF; - CH->SLOT[s].eve = EG_OFF + 1; - CH->SLOT[s].evs = 0; - } - } -} - -/* ---------- Create a virtual YM3812 ---------- */ -/* 'rate' is sampling rate and 'bufsiz' is the size of the */ -FM_OPL *OPLCreate(int type, int clock, int rate) { - char *ptr; - FM_OPL *OPL; - int state_size; - int max_ch = 9; /* normaly 9 channels */ - - if (OPL_LockTable() == -1) - return NULL; - /* allocate OPL state space */ - state_size = sizeof(FM_OPL); - state_size += sizeof(OPL_CH) * max_ch; - - /* allocate memory block */ - ptr = (char *)calloc(state_size, 1); - if (ptr == NULL) - return NULL; - - /* clear */ - memset(ptr, 0, state_size); - OPL = (FM_OPL *)ptr; ptr += sizeof(FM_OPL); - OPL->P_CH = (OPL_CH *)ptr; ptr += sizeof(OPL_CH) * max_ch; - - /* set channel state pointer */ - OPL->type = type; - OPL->clock = clock; - OPL->rate = rate; - OPL->max_ch = max_ch; - - /* init grobal tables */ - OPL_initalize(OPL); - - /* reset chip */ - OPLResetChip(OPL); - return OPL; -} - -/* ---------- Destroy one of vietual YM3812 ---------- */ -void OPLDestroy(FM_OPL *OPL) { - OPL_UnLockTable(); - free(OPL); -} - -/* ---------- Option handlers ---------- */ -void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler,int channelOffset) { - OPL->TimerHandler = TimerHandler; - OPL->TimerParam = channelOffset; -} - -void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param) { - OPL->IRQHandler = IRQHandler; - OPL->IRQParam = param; -} - -void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler,int param) { - OPL->UpdateHandler = UpdateHandler; - OPL->UpdateParam = param; -} - -/* ---------- YM3812 I/O interface ---------- */ -int OPLWrite(FM_OPL *OPL,int a,int v) { - if (!(a & 1)) { /* address port */ - OPL->address = v & 0xff; - } else { /* data port */ - if (OPL->UpdateHandler) - OPL->UpdateHandler(OPL->UpdateParam,0); - OPLWriteReg(OPL, OPL->address,v); - } - return OPL->status >> 7; -} - -unsigned char OPLRead(FM_OPL *OPL,int a) { - if (!(a & 1)) { /* status port */ - return OPL->status & (OPL->statusmask | 0x80); - } - /* data port */ - switch (OPL->address) { - case 0x05: /* KeyBoard IN */ - warning("OPL:read unmapped KEYBOARD port"); - return 0; - case 0x19: /* I/O DATA */ - warning("OPL:read unmapped I/O port"); - return 0; - case 0x1a: /* PCM-DATA */ - return 0; - default: - break; - } - return 0; -} - -int OPLTimerOver(FM_OPL *OPL, int c) { - if (c) { /* Timer B */ - OPL_STATUS_SET(OPL, 0x20); - } else { /* Timer A */ - OPL_STATUS_SET(OPL, 0x40); - /* CSM mode key,TL controll */ - if (OPL->mode & 0x80) { /* CSM mode total level latch and auto key on */ - int ch; - if (OPL->UpdateHandler) - OPL->UpdateHandler(OPL->UpdateParam,0); - for (ch = 0; ch < 9; ch++) - CSMKeyControll(&OPL->P_CH[ch]); - } - } - /* reload timer */ - if (OPL->TimerHandler) - (OPL->TimerHandler)(OPL->TimerParam + c, (double)OPL->T[c] * OPL->TimerBase); - return OPL->status >> 7; -} - -FM_OPL *makeAdLibOPL(int rate) { - // We need to emulate one YM3812 chip - int env_bits = FMOPL_ENV_BITS_HQ; - int eg_ent = FMOPL_EG_ENT_HQ; -#if defined (_WIN32_WCE) || defined(__SYMBIAN32__) || defined(__GP32__) || defined (GP2X) || defined(__MAEMO__) || defined(__DS__) || defined (__MINT__) || defined(__N64__) - if (ConfMan.hasKey("FM_high_quality") && ConfMan.getBool("FM_high_quality")) { - env_bits = FMOPL_ENV_BITS_HQ; - eg_ent = FMOPL_EG_ENT_HQ; - } else if (ConfMan.hasKey("FM_medium_quality") && ConfMan.getBool("FM_medium_quality")) { - env_bits = FMOPL_ENV_BITS_MQ; - eg_ent = FMOPL_EG_ENT_MQ; - } else { - env_bits = FMOPL_ENV_BITS_LQ; - eg_ent = FMOPL_EG_ENT_LQ; - } -#endif - - OPLBuildTables(env_bits, eg_ent); - return OPLCreate(OPL_TYPE_YM3812, 3579545, rate); -} - -} // End of namespace MAME -} // End of namespace OPL - diff --git a/sound/softsynth/opl/mame.h b/sound/softsynth/opl/mame.h deleted file mode 100644 index ec812d0c4e..0000000000 --- a/sound/softsynth/opl/mame.h +++ /dev/null @@ -1,202 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - * LGPL licensed version of MAMEs fmopl (V0.37a modified) by - * Tatsuyuki Satoh. Included from LGPL'ed AdPlug. - */ - - -#ifndef SOUND_SOFTSYNTH_OPL_MAME_H -#define SOUND_SOFTSYNTH_OPL_MAME_H - -#include "common/scummsys.h" -#include "common/util.h" -#include "common/random.h" - -#include "sound/fmopl.h" - -namespace OPL { -namespace MAME { - -enum { - FMOPL_ENV_BITS_HQ = 16, - FMOPL_ENV_BITS_MQ = 8, - FMOPL_ENV_BITS_LQ = 8, - FMOPL_EG_ENT_HQ = 4096, - FMOPL_EG_ENT_MQ = 1024, - FMOPL_EG_ENT_LQ = 128 -}; - - -typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); -typedef void (*OPL_IRQHANDLER)(int param,int irq); -typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); - -#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ - -/* Saving is necessary for member of the 'R' mark for suspend/resume */ -/* ---------- OPL one of slot ---------- */ -typedef struct fm_opl_slot { - int TL; /* total level :TL << 8 */ - int TLL; /* adjusted now TL */ - uint8 KSR; /* key scale rate :(shift down bit) */ - int *AR; /* attack rate :&AR_TABLE[AR<<2] */ - int *DR; /* decay rate :&DR_TABLE[DR<<2] */ - int SL; /* sustain level :SL_TABLE[SL] */ - int *RR; /* release rate :&DR_TABLE[RR<<2] */ - uint8 ksl; /* keyscale level :(shift down bits) */ - uint8 ksr; /* key scale rate :kcode>>KSR */ - uint mul; /* multiple :ML_TABLE[ML] */ - uint Cnt; /* frequency count */ - uint Incr; /* frequency step */ - - /* envelope generator state */ - uint8 eg_typ;/* envelope type flag */ - uint8 evm; /* envelope phase */ - int evc; /* envelope counter */ - int eve; /* envelope counter end point */ - int evs; /* envelope counter step */ - int evsa; /* envelope step for AR :AR[ksr] */ - int evsd; /* envelope step for DR :DR[ksr] */ - int evsr; /* envelope step for RR :RR[ksr] */ - - /* LFO */ - uint8 ams; /* ams flag */ - uint8 vib; /* vibrate flag */ - /* wave selector */ - int **wavetable; -} OPL_SLOT; - -/* ---------- OPL one of channel ---------- */ -typedef struct fm_opl_channel { - OPL_SLOT SLOT[2]; - uint8 CON; /* connection type */ - uint8 FB; /* feed back :(shift down bit)*/ - int *connect1; /* slot1 output pointer */ - int *connect2; /* slot2 output pointer */ - int op1_out[2]; /* slot1 output for selfeedback */ - - /* phase generator state */ - uint block_fnum; /* block+fnum */ - uint8 kcode; /* key code : KeyScaleCode */ - uint fc; /* Freq. Increment base */ - uint ksl_base; /* KeyScaleLevel Base step */ - uint8 keyon; /* key on/off flag */ -} OPL_CH; - -/* OPL state */ -typedef struct fm_opl_f { - uint8 type; /* chip type */ - int clock; /* master clock (Hz) */ - int rate; /* sampling rate (Hz) */ - double freqbase; /* frequency base */ - double TimerBase; /* Timer base time (==sampling time) */ - uint8 address; /* address register */ - uint8 status; /* status flag */ - uint8 statusmask; /* status mask */ - uint mode; /* Reg.08 : CSM , notesel,etc. */ - - /* Timer */ - int T[2]; /* timer counter */ - uint8 st[2]; /* timer enable */ - - /* FM channel slots */ - OPL_CH *P_CH; /* pointer of CH */ - int max_ch; /* maximum channel */ - - /* Rythm sention */ - uint8 rythm; /* Rythm mode , key flag */ - - /* time tables */ - int AR_TABLE[76]; /* atttack rate tables */ - int DR_TABLE[76]; /* decay rate tables */ - uint FN_TABLE[1024];/* fnumber -> increment counter */ - - /* LFO */ - int *ams_table; - int *vib_table; - int amsCnt; - int amsIncr; - int vibCnt; - int vibIncr; - - /* wave selector enable flag */ - uint8 wavesel; - - /* external event callback handler */ - OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ - int TimerParam; /* TIMER parameter */ - OPL_IRQHANDLER IRQHandler; /* IRQ handler */ - int IRQParam; /* IRQ parameter */ - OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */ - int UpdateParam; /* stream update parameter */ - - Common::RandomSource rnd; -} FM_OPL; - -/* ---------- Generic interface section ---------- */ -#define OPL_TYPE_YM3526 (0) -#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) - -void OPLBuildTables(int ENV_BITS_PARAM, int EG_ENT_PARAM); - -FM_OPL *OPLCreate(int type, int clock, int rate); -void OPLDestroy(FM_OPL *OPL); -void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler, int channelOffset); -void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param); -void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler, int param); - -void OPLResetChip(FM_OPL *OPL); -int OPLWrite(FM_OPL *OPL, int a, int v); -unsigned char OPLRead(FM_OPL *OPL, int a); -int OPLTimerOver(FM_OPL *OPL, int c); -void OPLWriteReg(FM_OPL *OPL, int r, int v); -void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length); - -// Factory method -FM_OPL *makeAdLibOPL(int rate); - -// OPL API implementation -class OPL : public ::OPL::OPL { -private: - FM_OPL *_opl; -public: - OPL() : _opl(0) {} - ~OPL(); - - bool init(int rate); - void reset(); - - void write(int a, int v); - byte read(int a); - - void writeReg(int r, int v); - - void readBuffer(int16 *buffer, int length); - bool isStereo() const { return false; } -}; - -} // End of namespace MAME -} // End of namespace OPL - -#endif diff --git a/sound/softsynth/pcspk.cpp b/sound/softsynth/pcspk.cpp deleted file mode 100644 index bdf9f112e8..0000000000 --- a/sound/softsynth/pcspk.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/* ScummVM - Graphic Adventure Engine -* -* ScummVM is the legal property of its developers, whose names -* are too numerous to list here. Please refer to the COPYRIGHT -* file distributed with this source distribution. -* -* This program is free software; you can redistribute it and/or -* modify it under the terms of the GNU General Public License -* as published by the Free Software Foundation; either version 2 -* of the License, or (at your option) any later version. - -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. - -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -* -* $URL$ -* $Id$ -* -*/ - -#include "sound/softsynth/pcspk.h" -#include "sound/null.h" - -namespace Audio { - -const PCSpeaker::generatorFunc PCSpeaker::generateWave[] = - {&PCSpeaker::generateSquare, &PCSpeaker::generateSine, - &PCSpeaker::generateSaw, &PCSpeaker::generateTriangle}; - -PCSpeaker::PCSpeaker(int rate) { - _rate = rate; - _wave = kWaveFormSquare; - _playForever = false; - _oscLength = 0; - _oscSamples = 0; - _remainingSamples = 0; - _mixedSamples = 0; - _volume = 255; -} - -PCSpeaker::~PCSpeaker() { -} - -void PCSpeaker::play(WaveForm wave, int freq, int32 length) { - Common::StackLock lock(_mutex); - - assert((wave >= kWaveFormSquare) && (wave <= kWaveFormTriangle)); - - _wave = wave; - _oscLength = _rate / freq; - _oscSamples = 0; - if (length == -1) { - _remainingSamples = 1; - _playForever = true; - } else { - _remainingSamples = (_rate * length) / 1000; - _playForever = false; - } - _mixedSamples = 0; -} - -void PCSpeaker::stop(int32 delay) { - Common::StackLock lock(_mutex); - - _remainingSamples = (_rate * delay) / 1000; - _playForever = false; -} - -void PCSpeaker::setVolume(byte volume) { - _volume = volume; -} - -bool PCSpeaker::isPlaying() const { - return _remainingSamples != 0; -} - -int PCSpeaker::readBuffer(int16 *buffer, const int numSamples) { - Common::StackLock lock(_mutex); - - int i; - - for (i = 0; _remainingSamples && (i < numSamples); i++) { - buffer[i] = generateWave[_wave](_oscSamples, _oscLength) * _volume; - if (_oscSamples++ >= _oscLength) - _oscSamples = 0; - if (!_playForever) - _remainingSamples--; - _mixedSamples++; - } - - // Clear the rest of the buffer - if (i < numSamples) - memset(buffer + i, 0, (numSamples - i) * sizeof(int16)); - - return numSamples; -} - -int8 PCSpeaker::generateSquare(uint32 x, uint32 oscLength) { - return (x < (oscLength / 2)) ? 127 : -128; -} - -int8 PCSpeaker::generateSine(uint32 x, uint32 oscLength) { - if (oscLength == 0) - return 0; - - // TODO: Maybe using a look-up-table would be better? - return CLIP((int16) (128 * sin(2.0 * PI * x / oscLength)), -128, 127); -} - -int8 PCSpeaker::generateSaw(uint32 x, uint32 oscLength) { - if (oscLength == 0) - return 0; - - return ((x * (65536 / oscLength)) >> 8) - 128; -} - -int8 PCSpeaker::generateTriangle(uint32 x, uint32 oscLength) { - if (oscLength == 0) - return 0; - - int y = ((x * (65536 / (oscLength / 2))) >> 8) - 128; - - return (x <= (oscLength / 2)) ? y : (256 - y); -} - -} // End of namespace Audio - - -// Plugin interface -// (This can only create a null driver since pc speaker support is not part of the -// midi driver architecture. But we need the plugin for the options menu in the launcher -// and for MidiDriver::detectDevice() which is more or less used by all engines.) - -class PCSpeakerMusicPlugin : public NullMusicPlugin { -public: - const char *getName() const { - return _s("PC Speaker Emulator"); - } - - const char *getId() const { - return "pcspk"; - } - - MusicDevices getDevices() const; -}; - -MusicDevices PCSpeakerMusicPlugin::getDevices() const { - MusicDevices devices; - devices.push_back(MusicDevice(this, "", MT_PCSPK)); - return devices; -} - -class PCjrMusicPlugin : public NullMusicPlugin { -public: - const char *getName() const { - return _s("IBM PCjr Emulator"); - } - - const char *getId() const { - return "pcjr"; - } - - MusicDevices getDevices() const; -}; - -MusicDevices PCjrMusicPlugin::getDevices() const { - MusicDevices devices; - devices.push_back(MusicDevice(this, "", MT_PCJR)); - return devices; -} - -//#if PLUGIN_ENABLED_DYNAMIC(PCSPK) - //REGISTER_PLUGIN_DYNAMIC(PCSPK, PLUGIN_TYPE_MUSIC, PCSpeakerMusicPlugin); -//#else - REGISTER_PLUGIN_STATIC(PCSPK, PLUGIN_TYPE_MUSIC, PCSpeakerMusicPlugin); -//#endif - -//#if PLUGIN_ENABLED_DYNAMIC(PCJR) - //REGISTER_PLUGIN_DYNAMIC(PCJR, PLUGIN_TYPE_MUSIC, PCjrMusicPlugin); -//#else - REGISTER_PLUGIN_STATIC(PCJR, PLUGIN_TYPE_MUSIC, PCjrMusicPlugin); -//#endif diff --git a/sound/softsynth/pcspk.h b/sound/softsynth/pcspk.h deleted file mode 100644 index 3cd8734a7e..0000000000 --- a/sound/softsynth/pcspk.h +++ /dev/null @@ -1,88 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#ifndef SOUND_SOFTSYNTH_PCSPK_H -#define SOUND_SOFTSYNTH_PCSPK_H - -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "common/mutex.h" - -namespace Audio { - -class PCSpeaker : public AudioStream { -public: - enum WaveForm { - kWaveFormSquare = 0, - kWaveFormSine, - kWaveFormSaw, - kWaveFormTriangle - }; - - PCSpeaker(int rate = 44100); - ~PCSpeaker(); - - /** Play a note for length ms. - * - * If length is negative, play until told to stop. - */ - void play(WaveForm wave, int freq, int32 length); - /** Stop the currently playing note after delay ms. */ - void stop(int32 delay = 0); - /** Adjust the volume. */ - void setVolume(byte volume); - - bool isPlaying() const; - - int readBuffer(int16 *buffer, const int numSamples); - - bool isStereo() const { return false; } - bool endOfData() const { return false; } - bool endOfStream() const { return false; } - int getRate() const { return _rate; } - -protected: - Common::Mutex _mutex; - - int _rate; - WaveForm _wave; - bool _playForever; - uint32 _oscLength; - uint32 _oscSamples; - uint32 _remainingSamples; - uint32 _mixedSamples; - byte _volume; - - typedef int8 (*generatorFunc)(uint32, uint32); - static const generatorFunc generateWave[]; - - static int8 generateSquare(uint32 x, uint32 oscLength); - static int8 generateSine(uint32 x, uint32 oscLength); - static int8 generateSaw(uint32 x, uint32 oscLength); - static int8 generateTriangle(uint32 x, uint32 oscLength); -}; - -} // End of namespace Audio - -#endif // SOUND_SOFTSYNTH_PCSPEAKER_H diff --git a/sound/softsynth/sid.cpp b/sound/softsynth/sid.cpp deleted file mode 100644 index c05ae2e8f5..0000000000 --- a/sound/softsynth/sid.cpp +++ /dev/null @@ -1,1456 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/* - * This file is based on reSID, a MOS6581 SID emulator engine. - * Copyright (C) 2004 Dag Lem - */ - -#ifndef DISABLE_SID - -#include "sid.h" -#include "sound/null.h" -#include - -namespace Resid { - -// Fixpoint constants (16.16 bits). -const int SID::FIXP_SHIFT = 16; -const int SID::FIXP_MASK = 0xffff; - -/* - * WaveformGenerator - */ - -WaveformGenerator::WaveformGenerator() { - sync_source = this; - - reset(); -} - -void WaveformGenerator::set_sync_source(WaveformGenerator* source) { - sync_source = source; - source->sync_dest = this; -} - -void WaveformGenerator::writeFREQ_LO(reg8 freq_lo) { - freq = (freq & 0xff00) | (freq_lo & 0x00ff); -} - -void WaveformGenerator::writeFREQ_HI(reg8 freq_hi) { - freq = ((freq_hi << 8) & 0xff00) | (freq & 0x00ff); -} - -void WaveformGenerator::writePW_LO(reg8 pw_lo) { - pw = (pw & 0xf00) | (pw_lo & 0x0ff); -} - -void WaveformGenerator::writePW_HI(reg8 pw_hi) { - pw = ((pw_hi << 8) & 0xf00) | (pw & 0x0ff); -} - -void WaveformGenerator::writeCONTROL_REG(reg8 control) { - waveform = (control >> 4) & 0x0f; - ring_mod = control & 0x04; - sync = control & 0x02; - - reg8 test_next = control & 0x08; - - // Test bit set. - if (test_next) { - accumulator = 0; - shift_register = 0; - } - // Test bit cleared. - else if (test) { - shift_register = 0x7ffff8; - } - - test = test_next; - - // The gate bit is handled by the EnvelopeGenerator. -} - -reg8 WaveformGenerator::readOSC() { - return output() >> 4; -} - -void WaveformGenerator::reset() { - accumulator = 0; - shift_register = 0x7ffff8; - freq = 0; - pw = 0; - - test = 0; - ring_mod = 0; - sync = 0; - - msb_rising = false; -} - -RESID_INLINE void WaveformGenerator::clock(cycle_count delta_t) { - // No operation if test bit is set. - if (test) { - return; - } - - reg24 accumulator_prev = accumulator; - - // Calculate new accumulator value; - reg24 delta_accumulator = delta_t*freq; - accumulator += delta_accumulator; - accumulator &= 0xffffff; - - // Check whether the MSB is set high. This is used for synchronization. - msb_rising = !(accumulator_prev & 0x800000) && (accumulator & 0x800000); - - // Shift noise register once for each time accumulator bit 19 is set high. - // Bit 19 is set high each time 2^20 (0x100000) is added to the accumulator. - reg24 shift_period = 0x100000; - - while (delta_accumulator) { - if (delta_accumulator < shift_period) { - shift_period = delta_accumulator; - // Determine whether bit 19 is set on the last period. - // NB! Requires two's complement integer. - if (shift_period <= 0x080000) { - // Check for flip from 0 to 1. - if (((accumulator - shift_period) & 0x080000) || !(accumulator & 0x080000)) - { - break; - } - } - else { - // Check for flip from 0 (to 1 or via 1 to 0) or from 1 via 0 to 1. - if (((accumulator - shift_period) & 0x080000) && !(accumulator & 0x080000)) - { - break; - } - } - } - - // Shift the noise/random register. - // NB! The shift is actually delayed 2 cycles, this is not modeled. - reg24 bit0 = ((shift_register >> 22) ^ (shift_register >> 17)) & 0x1; - shift_register <<= 1; - shift_register &= 0x7fffff; - shift_register |= bit0; - - delta_accumulator -= shift_period; - } -} - - -/** - * Synchronize oscillators. - * This must be done after all the oscillators have been clock()'ed since the - * oscillators operate in parallel. - * Note that the oscillators must be clocked exactly on the cycle when the - * MSB is set high for hard sync to operate correctly. See SID::clock(). - */ -RESID_INLINE void WaveformGenerator::synchronize() { - // A special case occurs when a sync source is synced itself on the same - // cycle as when its MSB is set high. In this case the destination will - // not be synced. This has been verified by sampling OSC3. - if (msb_rising && sync_dest->sync && !(sync && sync_source->msb_rising)) { - sync_dest->accumulator = 0; - } -} - - -/* - * Output functions - */ - -// No waveform: Zero output. -RESID_INLINE reg12 WaveformGenerator::output____() { - return 0x000; -} - -// Triangle: -RESID_INLINE reg12 WaveformGenerator::output___T() { - reg24 msb = (ring_mod ? accumulator ^ sync_source->accumulator : accumulator) - & 0x800000; - return ((msb ? ~accumulator : accumulator) >> 11) & 0xfff; -} - -// Sawtooth: -RESID_INLINE reg12 WaveformGenerator::output__S_() { - return accumulator >> 12; -} - -// Pulse: -RESID_INLINE reg12 WaveformGenerator::output_P__() { - return (test || (accumulator >> 12) >= pw) ? 0xfff : 0x000; -} - -// Noise: -RESID_INLINE reg12 WaveformGenerator::outputN___() { - return - ((shift_register & 0x400000) >> 11) | - ((shift_register & 0x100000) >> 10) | - ((shift_register & 0x010000) >> 7) | - ((shift_register & 0x002000) >> 5) | - ((shift_register & 0x000800) >> 4) | - ((shift_register & 0x000080) >> 1) | - ((shift_register & 0x000010) << 1) | - ((shift_register & 0x000004) << 2); -} - -// Combined waveforms: - -RESID_INLINE reg12 WaveformGenerator::output__ST() { - return wave6581__ST[output__S_()] << 4; -} - -RESID_INLINE reg12 WaveformGenerator::output_P_T() { - return (wave6581_P_T[output___T() >> 1] << 4) & output_P__(); -} - -RESID_INLINE reg12 WaveformGenerator::output_PS_() { - return (wave6581_PS_[output__S_()] << 4) & output_P__(); -} - -RESID_INLINE reg12 WaveformGenerator::output_PST() { - return (wave6581_PST[output__S_()] << 4) & output_P__(); -} - -// Combined waveforms including noise: - -RESID_INLINE reg12 WaveformGenerator::outputN__T() { - return 0; -} - -RESID_INLINE reg12 WaveformGenerator::outputN_S_() { - return 0; -} - -RESID_INLINE reg12 WaveformGenerator::outputN_ST() { - return 0; -} - -RESID_INLINE reg12 WaveformGenerator::outputNP__() { - return 0; -} - -RESID_INLINE reg12 WaveformGenerator::outputNP_T() { - return 0; -} - -RESID_INLINE reg12 WaveformGenerator::outputNPS_() { - return 0; -} - -RESID_INLINE reg12 WaveformGenerator::outputNPST() { - return 0; -} - -/** - * Select one of 16 possible combinations of waveforms. - */ -RESID_INLINE reg12 WaveformGenerator::output() { - // It may seem cleaner to use an array of member functions to return - // waveform output; however a switch with inline functions is faster. - - switch (waveform) { - default: - case 0x0: - return output____(); - case 0x1: - return output___T(); - case 0x2: - return output__S_(); - case 0x3: - return output__ST(); - case 0x4: - return output_P__(); - case 0x5: - return output_P_T(); - case 0x6: - return output_PS_(); - case 0x7: - return output_PST(); - case 0x8: - return outputN___(); - case 0x9: - return outputN__T(); - case 0xa: - return outputN_S_(); - case 0xb: - return outputN_ST(); - case 0xc: - return outputNP__(); - case 0xd: - return outputNP_T(); - case 0xe: - return outputNPS_(); - case 0xf: - return outputNPST(); - } -} - -/* - * Our objective is to construct a smooth interpolating single-valued function - * y = f(x). - * Our approach is to approximate the properties of Catmull-Rom splines for - * piecewice cubic polynomials. - */ - -/** - * Calculation of coefficients. - */ -inline void cubic_coefficients(double x1, double y1, double x2, double y2, - double k1, double k2, - double& a, double& b, double& c, double& d) -{ - double dx = x2 - x1, dy = y2 - y1; - - a = ((k1 + k2) - 2*dy/dx)/(dx*dx); - b = ((k2 - k1)/dx - 3*(x1 + x2)*a)/2; - c = k1 - (3*x1*a + 2*b)*x1; - d = y1 - ((x1*a + b)*x1 + c)*x1; -} - -/** - * Evaluation of cubic polynomial by forward differencing. - */ -template -inline void interpolate_segment(double x1, double y1, double x2, double y2, - double k1, double k2, - PointPlotter plot, double res) -{ - double a, b, c, d; - cubic_coefficients(x1, y1, x2, y2, k1, k2, a, b, c, d); - - double y = ((a*x1 + b)*x1 + c)*x1 + d; - double dy = (3*a*(x1 + res) + 2*b)*x1*res + ((a*res + b)*res + c)*res; - double d2y = (6*a*(x1 + res) + 2*b)*res*res; - double d3y = 6*a*res*res*res; - - // Calculate each point. - for (double x = x1; x <= x2; x += res) { - plot(x, y); - y += dy; dy += d2y; d2y += d3y; - } -} - -template -inline double x(PointIter p) { - return (*p)[0]; -} - -template -inline double y(PointIter p) { - return (*p)[1]; -} - -/** - * Evaluation of complete interpolating function. - * Note that since each curve segment is controlled by four points, the - * end points will not be interpolated. If extra control points are not - * desirable, the end points can simply be repeated to ensure interpolation. - * Note also that points of non-differentiability and discontinuity can be - * introduced by repeating points. - */ -template -inline void interpolate(PointIter p0, PointIter pn, PointPlotter plot, double res) { - double k1, k2; - - // Set up points for first curve segment. - PointIter p1 = p0; ++p1; - PointIter p2 = p1; ++p2; - PointIter p3 = p2; ++p3; - - // Draw each curve segment. - for (; p2 != pn; ++p0, ++p1, ++p2, ++p3) { - // p1 and p2 equal; single point. - if (x(p1) == x(p2)) { - continue; - } - // Both end points repeated; straight line. - if (x(p0) == x(p1) && x(p2) == x(p3)) { - k1 = k2 = (y(p2) - y(p1))/(x(p2) - x(p1)); - } - // p0 and p1 equal; use f''(x1) = 0. - else if (x(p0) == x(p1)) { - k2 = (y(p3) - y(p1))/(x(p3) - x(p1)); - k1 = (3*(y(p2) - y(p1))/(x(p2) - x(p1)) - k2)/2; - } - // p2 and p3 equal; use f''(x2) = 0. - else if (x(p2) == x(p3)) { - k1 = (y(p2) - y(p0))/(x(p2) - x(p0)); - k2 = (3*(y(p2) - y(p1))/(x(p2) - x(p1)) - k1)/2; - } - // Normal curve. - else { - k1 = (y(p2) - y(p0))/(x(p2) - x(p0)); - k2 = (y(p3) - y(p1))/(x(p3) - x(p1)); - } - - interpolate_segment(x(p1), y(p1), x(p2), y(p2), k1, k2, plot, res); - } -} - -/** - * Class for plotting integers into an array. - */ -template -class PointPlotter { -protected: - F* f; - -public: - PointPlotter(F* arr) : f(arr) { - } - - void operator ()(double x, double y) { - // Clamp negative values to zero. - if (y < 0) { - y = 0; - } - - f[F(x)] = F(y); - } -}; - -fc_point Filter::f0_points_6581[] = { - // FC f FCHI FCLO - // ---------------------------- - { 0, 220 }, // 0x00 - repeated end point - { 0, 220 }, // 0x00 - { 128, 230 }, // 0x10 - { 256, 250 }, // 0x20 - { 384, 300 }, // 0x30 - { 512, 420 }, // 0x40 - { 640, 780 }, // 0x50 - { 768, 1600 }, // 0x60 - { 832, 2300 }, // 0x68 - { 896, 3200 }, // 0x70 - { 960, 4300 }, // 0x78 - { 992, 5000 }, // 0x7c - { 1008, 5400 }, // 0x7e - { 1016, 5700 }, // 0x7f - { 1023, 6000 }, // 0x7f 0x07 - { 1023, 6000 }, // 0x7f 0x07 - discontinuity - { 1024, 4600 }, // 0x80 - - { 1024, 4600 }, // 0x80 - { 1032, 4800 }, // 0x81 - { 1056, 5300 }, // 0x84 - { 1088, 6000 }, // 0x88 - { 1120, 6600 }, // 0x8c - { 1152, 7200 }, // 0x90 - { 1280, 9500 }, // 0xa0 - { 1408, 12000 }, // 0xb0 - { 1536, 14500 }, // 0xc0 - { 1664, 16000 }, // 0xd0 - { 1792, 17100 }, // 0xe0 - { 1920, 17700 }, // 0xf0 - { 2047, 18000 }, // 0xff 0x07 - { 2047, 18000 } // 0xff 0x07 - repeated end point -}; - - -/* - * Filter - */ - -Filter::Filter() { - fc = 0; - - res = 0; - - filt = 0; - - voice3off = 0; - - hp_bp_lp = 0; - - vol = 0; - - // State of filter. - Vhp = 0; - Vbp = 0; - Vlp = 0; - Vnf = 0; - - enable_filter(true); - - // Create mappings from FC to cutoff frequency. - interpolate(f0_points_6581, f0_points_6581 - + sizeof(f0_points_6581)/sizeof(*f0_points_6581) - 1, - PointPlotter(f0_6581), 1.0); - - mixer_DC = (-0xfff*0xff/18) >> 7; - - f0 = f0_6581; - f0_points = f0_points_6581; - f0_count = sizeof(f0_points_6581)/sizeof(*f0_points_6581); - - set_w0(); - set_Q(); -} - -void Filter::enable_filter(bool enable) { - enabled = enable; -} - -void Filter::reset(){ - fc = 0; - - res = 0; - - filt = 0; - - voice3off = 0; - - hp_bp_lp = 0; - - vol = 0; - - // State of filter. - Vhp = 0; - Vbp = 0; - Vlp = 0; - Vnf = 0; - - set_w0(); - set_Q(); -} - -void Filter::writeFC_LO(reg8 fc_lo) { - fc = (fc & 0x7f8) | (fc_lo & 0x007); - set_w0(); -} - -void Filter::writeFC_HI(reg8 fc_hi) { - fc = ((fc_hi << 3) & 0x7f8) | (fc & 0x007); - set_w0(); -} - -void Filter::writeRES_FILT(reg8 res_filt) { - res = (res_filt >> 4) & 0x0f; - set_Q(); - - filt = res_filt & 0x0f; -} - -void Filter::writeMODE_VOL(reg8 mode_vol) { - voice3off = mode_vol & 0x80; - - hp_bp_lp = (mode_vol >> 4) & 0x07; - - vol = mode_vol & 0x0f; -} - -// Set filter cutoff frequency. -void Filter::set_w0() { - const double pi = 3.1415926535897932385; - - // Multiply with 1.048576 to facilitate division by 1 000 000 by right- - // shifting 20 times (2 ^ 20 = 1048576). - w0 = static_cast(2*pi*f0[fc]*1.048576); - - // Limit f0 to 16kHz to keep 1 cycle filter stable. - const sound_sample w0_max_1 = static_cast(2*pi*16000*1.048576); - w0_ceil_1 = w0 <= w0_max_1 ? w0 : w0_max_1; - - // Limit f0 to 4kHz to keep delta_t cycle filter stable. - const sound_sample w0_max_dt = static_cast(2*pi*4000*1.048576); - w0_ceil_dt = w0 <= w0_max_dt ? w0 : w0_max_dt; -} - -// Set filter resonance. -void Filter::set_Q() { - // Q is controlled linearly by res. Q has approximate range [0.707, 1.7]. - // As resonance is increased, the filter must be clocked more often to keep - // stable. - - // The coefficient 1024 is dispensed of later by right-shifting 10 times - // (2 ^ 10 = 1024). - _1024_div_Q = static_cast(1024.0/(0.707 + 1.0*res/0x0f)); -} - -RESID_INLINE void Filter::clock(cycle_count delta_t, - sound_sample voice1, - sound_sample voice2, - sound_sample voice3) -{ - // Scale each voice down from 20 to 13 bits. - voice1 >>= 7; - voice2 >>= 7; - - // NB! Voice 3 is not silenced by voice3off if it is routed through - // the filter. - if (voice3off && !(filt & 0x04)) { - voice3 = 0; - } - else { - voice3 >>= 7; - } - - // Enable filter on/off. - // This is not really part of SID, but is useful for testing. - // On slow CPUs it may be necessary to bypass the filter to lower the CPU - // load. - if (!enabled) { - Vnf = voice1 + voice2 + voice3; - Vhp = Vbp = Vlp = 0; - return; - } - - // Route voices into or around filter. - // The code below is expanded to a switch for faster execution. - // (filt1 ? Vi : Vnf) += voice1; - // (filt2 ? Vi : Vnf) += voice2; - // (filt3 ? Vi : Vnf) += voice3; - - sound_sample Vi; - - switch (filt) { - default: - case 0x0: - Vi = 0; - Vnf = voice1 + voice2 + voice3; - break; - case 0x1: - Vi = voice1; - Vnf = voice2 + voice3; - break; - case 0x2: - Vi = voice2; - Vnf = voice1 + voice3; - break; - case 0x3: - Vi = voice1 + voice2; - Vnf = voice3; - break; - case 0x4: - Vi = voice3; - Vnf = voice1 + voice2; - break; - case 0x5: - Vi = voice1 + voice3; - Vnf = voice2; - break; - case 0x6: - Vi = voice2 + voice3; - Vnf = voice1; - break; - case 0x7: - Vi = voice1 + voice2 + voice3; - Vnf = 0; - break; - case 0x8: - Vi = 0; - Vnf = voice1 + voice2 + voice3; - break; - case 0x9: - Vi = voice1; - Vnf = voice2 + voice3; - break; - case 0xa: - Vi = voice2; - Vnf = voice1 + voice3; - break; - case 0xb: - Vi = voice1 + voice2; - Vnf = voice3; - break; - case 0xc: - Vi = voice3; - Vnf = voice1 + voice2; - break; - case 0xd: - Vi = voice1 + voice3; - Vnf = voice2; - break; - case 0xe: - Vi = voice2 + voice3; - Vnf = voice1; - break; - case 0xf: - Vi = voice1 + voice2 + voice3; - Vnf = 0; - break; - } - - // Maximum delta cycles for the filter to work satisfactorily under current - // cutoff frequency and resonance constraints is approximately 8. - cycle_count delta_t_flt = 8; - - while (delta_t) { - if (delta_t < delta_t_flt) { - delta_t_flt = delta_t; - } - - // delta_t is converted to seconds given a 1MHz clock by dividing - // with 1 000 000. This is done in two operations to avoid integer - // multiplication overflow. - - // Calculate filter outputs. - // Vhp = Vbp/Q - Vlp - Vi; - // dVbp = -w0*Vhp*dt; - // dVlp = -w0*Vbp*dt; - sound_sample w0_delta_t = w0_ceil_dt*delta_t_flt >> 6; - - sound_sample dVbp = (w0_delta_t*Vhp >> 14); - sound_sample dVlp = (w0_delta_t*Vbp >> 14); - Vbp -= dVbp; - Vlp -= dVlp; - Vhp = (Vbp*_1024_div_Q >> 10) - Vlp - Vi; - - delta_t -= delta_t_flt; - } -} - -RESID_INLINE sound_sample Filter::output() { - // This is handy for testing. - if (!enabled) { - return (Vnf + mixer_DC)*static_cast(vol); - } - - // Mix highpass, bandpass, and lowpass outputs. The sum is not - // weighted, this can be confirmed by sampling sound output for - // e.g. bandpass, lowpass, and bandpass+lowpass from a SID chip. - - // The code below is expanded to a switch for faster execution. - // if (hp) Vf += Vhp; - // if (bp) Vf += Vbp; - // if (lp) Vf += Vlp; - - sound_sample Vf; - - switch (hp_bp_lp) { - default: - case 0x0: - Vf = 0; - break; - case 0x1: - Vf = Vlp; - break; - case 0x2: - Vf = Vbp; - break; - case 0x3: - Vf = Vlp + Vbp; - break; - case 0x4: - Vf = Vhp; - break; - case 0x5: - Vf = Vlp + Vhp; - break; - case 0x6: - Vf = Vbp + Vhp; - break; - case 0x7: - Vf = Vlp + Vbp + Vhp; - break; - } - - // Sum non-filtered and filtered output. - // Multiply the sum with volume. - return (Vnf + Vf + mixer_DC)*static_cast(vol); -} - - -/* - * EnvelopeGenerator - */ - -EnvelopeGenerator::EnvelopeGenerator() { - reset(); -} - -void EnvelopeGenerator::reset() { - envelope_counter = 0; - - attack = 0; - decay = 0; - sustain = 0; - release = 0; - - gate = 0; - - rate_counter = 0; - exponential_counter = 0; - exponential_counter_period = 1; - - state = RELEASE; - rate_period = rate_counter_period[release]; - hold_zero = true; -} - -reg16 EnvelopeGenerator::rate_counter_period[] = { - 9, // 2ms*1.0MHz/256 = 7.81 - 32, // 8ms*1.0MHz/256 = 31.25 - 63, // 16ms*1.0MHz/256 = 62.50 - 95, // 24ms*1.0MHz/256 = 93.75 - 149, // 38ms*1.0MHz/256 = 148.44 - 220, // 56ms*1.0MHz/256 = 218.75 - 267, // 68ms*1.0MHz/256 = 265.63 - 313, // 80ms*1.0MHz/256 = 312.50 - 392, // 100ms*1.0MHz/256 = 390.63 - 977, // 250ms*1.0MHz/256 = 976.56 - 1954, // 500ms*1.0MHz/256 = 1953.13 - 3126, // 800ms*1.0MHz/256 = 3125.00 - 3907, // 1 s*1.0MHz/256 = 3906.25 - 11720, // 3 s*1.0MHz/256 = 11718.75 - 19532, // 5 s*1.0MHz/256 = 19531.25 - 31251 // 8 s*1.0MHz/256 = 31250.00 -}; - - -reg8 EnvelopeGenerator::sustain_level[] = { - 0x00, - 0x11, - 0x22, - 0x33, - 0x44, - 0x55, - 0x66, - 0x77, - 0x88, - 0x99, - 0xaa, - 0xbb, - 0xcc, - 0xdd, - 0xee, - 0xff, -}; - -void EnvelopeGenerator::writeCONTROL_REG(reg8 control) { - reg8 gate_next = control & 0x01; - - // The rate counter is never reset, thus there will be a delay before the - // envelope counter starts counting up (attack) or down (release). - - // Gate bit on: Start attack, decay, sustain. - if (!gate && gate_next) { - state = ATTACK; - rate_period = rate_counter_period[attack]; - - // Switching to attack state unlocks the zero freeze. - hold_zero = false; - } - // Gate bit off: Start release. - else if (gate && !gate_next) { - state = RELEASE; - rate_period = rate_counter_period[release]; - } - - gate = gate_next; -} - -void EnvelopeGenerator::writeATTACK_DECAY(reg8 attack_decay) { - attack = (attack_decay >> 4) & 0x0f; - decay = attack_decay & 0x0f; - if (state == ATTACK) { - rate_period = rate_counter_period[attack]; - } - else if (state == DECAY_SUSTAIN) { - rate_period = rate_counter_period[decay]; - } -} - -void EnvelopeGenerator::writeSUSTAIN_RELEASE(reg8 sustain_release) { - sustain = (sustain_release >> 4) & 0x0f; - release = sustain_release & 0x0f; - if (state == RELEASE) { - rate_period = rate_counter_period[release]; - } -} - -reg8 EnvelopeGenerator::readENV() { - return output(); -} - -RESID_INLINE void EnvelopeGenerator::clock(cycle_count delta_t) { - // Check for ADSR delay bug. - // If the rate counter comparison value is set below the current value of the - // rate counter, the counter will continue counting up until it wraps around - // to zero at 2^15 = 0x8000, and then count rate_period - 1 before the - // envelope can finally be stepped. - // This has been verified by sampling ENV3. - // - - // NB! This requires two's complement integer. - int rate_step = rate_period - rate_counter; - if (rate_step <= 0) { - rate_step += 0x7fff; - } - - while (delta_t) { - if (delta_t < rate_step) { - rate_counter += delta_t; - if (rate_counter & 0x8000) { - ++rate_counter &= 0x7fff; - } - return; - } - - rate_counter = 0; - delta_t -= rate_step; - - // The first envelope step in the attack state also resets the exponential - // counter. This has been verified by sampling ENV3. - // - if (state == ATTACK || ++exponential_counter == exponential_counter_period) - { - exponential_counter = 0; - - // Check whether the envelope counter is frozen at zero. - if (hold_zero) { - rate_step = rate_period; - continue; - } - - switch (state) { - case ATTACK: - // The envelope counter can flip from 0xff to 0x00 by changing state to - // release, then to attack. The envelope counter is then frozen at - // zero; to unlock this situation the state must be changed to release, - // then to attack. This has been verified by sampling ENV3. - // - ++envelope_counter &= 0xff; - if (envelope_counter == 0xff) { - state = DECAY_SUSTAIN; - rate_period = rate_counter_period[decay]; - } - break; - case DECAY_SUSTAIN: - if (envelope_counter != sustain_level[sustain]) { - --envelope_counter; - } - break; - case RELEASE: - // The envelope counter can flip from 0x00 to 0xff by changing state to - // attack, then to release. The envelope counter will then continue - // counting down in the release state. - // This has been verified by sampling ENV3. - // NB! The operation below requires two's complement integer. - // - --envelope_counter &= 0xff; - break; - } - - // Check for change of exponential counter period. - switch (envelope_counter) { - case 0xff: - exponential_counter_period = 1; - break; - case 0x5d: - exponential_counter_period = 2; - break; - case 0x36: - exponential_counter_period = 4; - break; - case 0x1a: - exponential_counter_period = 8; - break; - case 0x0e: - exponential_counter_period = 16; - break; - case 0x06: - exponential_counter_period = 30; - break; - case 0x00: - exponential_counter_period = 1; - - // When the envelope counter is changed to zero, it is frozen at zero. - // This has been verified by sampling ENV3. - hold_zero = true; - break; - } - } - - rate_step = rate_period; - } -} - -RESID_INLINE reg8 EnvelopeGenerator::output() { - return envelope_counter; -} - - -/* - * ExternalFilter - */ - -ExternalFilter::ExternalFilter() { - reset(); - enable_filter(true); - set_sampling_parameter(15915.6); - mixer_DC = ((((0x800 - 0x380) + 0x800)*0xff*3 - 0xfff*0xff/18) >> 7)*0x0f; -} - -void ExternalFilter::enable_filter(bool enable) { - enabled = enable; -} - -void ExternalFilter::set_sampling_parameter(double pass_freq) { - static const double pi = 3.1415926535897932385; - - w0hp = 105; - w0lp = (sound_sample) (pass_freq * (2.0 * pi * 1.048576)); - if (w0lp > 104858) - w0lp = 104858; -} - -void ExternalFilter::reset() { - // State of filter. - Vlp = 0; - Vhp = 0; - Vo = 0; -} - -RESID_INLINE void ExternalFilter::clock(cycle_count delta_t, sound_sample Vi) { - // This is handy for testing. - if (!enabled) { - // Remove maximum DC level since there is no filter to do it. - Vlp = Vhp = 0; - Vo = Vi - mixer_DC; - return; - } - - // Maximum delta cycles for the external filter to work satisfactorily - // is approximately 8. - cycle_count delta_t_flt = 8; - - while (delta_t) { - if (delta_t < delta_t_flt) { - delta_t_flt = delta_t; - } - - // delta_t is converted to seconds given a 1MHz clock by dividing - // with 1 000 000. - - // Calculate filter outputs. - // Vo = Vlp - Vhp; - // Vlp = Vlp + w0lp*(Vi - Vlp)*delta_t; - // Vhp = Vhp + w0hp*(Vlp - Vhp)*delta_t; - - sound_sample dVlp = (w0lp*delta_t_flt >> 8)*(Vi - Vlp) >> 12; - sound_sample dVhp = w0hp*delta_t_flt*(Vlp - Vhp) >> 20; - Vo = Vlp - Vhp; - Vlp += dVlp; - Vhp += dVhp; - - delta_t -= delta_t_flt; - } -} - -RESID_INLINE sound_sample ExternalFilter::output() { - return Vo; -} - - -/* - * Voice - */ - -Voice::Voice() { - wave_zero = 0x380; - voice_DC = 0x800*0xff; -} - -void Voice::set_sync_source(Voice* source) { - wave.set_sync_source(&source->wave); -} - -void Voice::writeCONTROL_REG(reg8 control) { - wave.writeCONTROL_REG(control); - envelope.writeCONTROL_REG(control); -} - -void Voice::reset() { - wave.reset(); - envelope.reset(); -} - - -/* - * SID - */ - -SID::SID() { - voice[0].set_sync_source(&voice[2]); - voice[1].set_sync_source(&voice[0]); - voice[2].set_sync_source(&voice[1]); - - set_sampling_parameters(985248, 44100); - - bus_value = 0; - bus_value_ttl = 0; -} - -SID::~SID() {} - -void SID::reset() { - for (int i = 0; i < 3; i++) { - voice[i].reset(); - } - filter.reset(); - extfilt.reset(); - - bus_value = 0; - bus_value_ttl = 0; -} - -int SID::output() { - const int range = 1 << 16; - const int half = range >> 1; - int sample = extfilt.output()/((4095*255 >> 7)*3*15*2/range); - if (sample >= half) { - return half - 1; - } - if (sample < -half) { - return -half; - } - return sample; -} - - -/** - * Read registers. - * - * Reading a write only register returns the last byte written to any SID - * register. The individual bits in this value start to fade down towards - * zero after a few cycles. All bits reach zero within approximately - * $2000 - $4000 cycles. - * It has been claimed that this fading happens in an orderly fashion, however - * sampling of write only registers reveals that this is not the case. - * NB! This is not correctly modeled. - * The actual use of write only registers has largely been made in the belief - * that all SID registers are readable. To support this belief the read - * would have to be done immediately after a write to the same register - * (remember that an intermediate write to another register would yield that - * value instead). With this in mind we return the last value written to - * any SID register for $2000 cycles without modeling the bit fading. - */ -reg8 SID::read(reg8 offset) { - switch (offset) { - case 0x19: - case 0x1a: - return 0; //readPOT(); - case 0x1b: - return voice[2].wave.readOSC(); - case 0x1c: - return voice[2].envelope.readENV(); - default: - return bus_value; - } -} - -void SID::write(reg8 offset, reg8 value) { - bus_value = value; - bus_value_ttl = 0x2000; - - switch (offset) { - case 0x00: - voice[0].wave.writeFREQ_LO(value); - break; - case 0x01: - voice[0].wave.writeFREQ_HI(value); - break; - case 0x02: - voice[0].wave.writePW_LO(value); - break; - case 0x03: - voice[0].wave.writePW_HI(value); - break; - case 0x04: - voice[0].writeCONTROL_REG(value); - break; - case 0x05: - voice[0].envelope.writeATTACK_DECAY(value); - break; - case 0x06: - voice[0].envelope.writeSUSTAIN_RELEASE(value); - break; - case 0x07: - voice[1].wave.writeFREQ_LO(value); - break; - case 0x08: - voice[1].wave.writeFREQ_HI(value); - break; - case 0x09: - voice[1].wave.writePW_LO(value); - break; - case 0x0a: - voice[1].wave.writePW_HI(value); - break; - case 0x0b: - voice[1].writeCONTROL_REG(value); - break; - case 0x0c: - voice[1].envelope.writeATTACK_DECAY(value); - break; - case 0x0d: - voice[1].envelope.writeSUSTAIN_RELEASE(value); - break; - case 0x0e: - voice[2].wave.writeFREQ_LO(value); - break; - case 0x0f: - voice[2].wave.writeFREQ_HI(value); - break; - case 0x10: - voice[2].wave.writePW_LO(value); - break; - case 0x11: - voice[2].wave.writePW_HI(value); - break; - case 0x12: - voice[2].writeCONTROL_REG(value); - break; - case 0x13: - voice[2].envelope.writeATTACK_DECAY(value); - break; - case 0x14: - voice[2].envelope.writeSUSTAIN_RELEASE(value); - break; - case 0x15: - filter.writeFC_LO(value); - break; - case 0x16: - filter.writeFC_HI(value); - break; - case 0x17: - filter.writeRES_FILT(value); - break; - case 0x18: - filter.writeMODE_VOL(value); - break; - default: - break; - } -} - -void SID::enable_filter(bool enable) { - filter.enable_filter(enable); -} - -void SID::enable_external_filter(bool enable) { - extfilt.enable_filter(enable); -} - - -/** - * Setting of SID sampling parameters. - * - * Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64. - * The default end of passband frequency is pass_freq = 0.9*sample_freq/2 - * for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample - * frequencies. - * - * For resampling, the ratio between the clock frequency and the sample - * frequency is limited as follows: - * 125*clock_freq/sample_freq < 16384 - * E.g. provided a clock frequency of ~ 1MHz, the sample frequency can not - * be set lower than ~ 8kHz. A lower sample frequency would make the - * resampling code overfill its 16k sample ring buffer. - * - * The end of passband frequency is also limited: - * pass_freq <= 0.9*sample_freq/2 - * - * E.g. for a 44.1kHz sampling rate the end of passband frequency is limited - * to slightly below 20kHz. This constraint ensures that the FIR table is - * not overfilled. - */ -bool SID::set_sampling_parameters(double clock_freq, - double sample_freq, double pass_freq, - double filter_scale) -{ - // The default passband limit is 0.9*sample_freq/2 for sample - // frequencies below ~ 44.1kHz, and 20kHz for higher sample frequencies. - if (pass_freq < 0) { - pass_freq = 20000; - if (2*pass_freq/sample_freq >= 0.9) { - pass_freq = 0.9*sample_freq/2; - } - } - // Check whether the FIR table would overfill. - else if (pass_freq > 0.9*sample_freq/2) { - return false; - } - - // The filter scaling is only included to avoid clipping, so keep - // it sane. - if (filter_scale < 0.9 || filter_scale > 1.0) { - return false; - } - - // Set the external filter to the pass freq - extfilt.set_sampling_parameter (pass_freq); - clock_frequency = clock_freq; - - cycles_per_sample = - cycle_count(clock_freq/sample_freq*(1 << FIXP_SHIFT) + 0.5); - - sample_offset = 0; - sample_prev = 0; - - return true; -} - -void SID::clock(cycle_count delta_t) { - int i; - - if (delta_t <= 0) { - return; - } - - // Age bus value. - bus_value_ttl -= delta_t; - if (bus_value_ttl <= 0) { - bus_value = 0; - bus_value_ttl = 0; - } - - // Clock amplitude modulators. - for (i = 0; i < 3; i++) { - voice[i].envelope.clock(delta_t); - } - - // Clock and synchronize oscillators. - // Loop until we reach the current cycle. - cycle_count delta_t_osc = delta_t; - while (delta_t_osc) { - cycle_count delta_t_min = delta_t_osc; - - // Find minimum number of cycles to an oscillator accumulator MSB toggle. - // We have to clock on each MSB on / MSB off for hard sync to operate - // correctly. - for (i = 0; i < 3; i++) { - WaveformGenerator& wave = voice[i].wave; - - // It is only necessary to clock on the MSB of an oscillator that is - // a sync source and has freq != 0. - if (!(wave.sync_dest->sync && wave.freq)) { - continue; - } - - reg16 freq = wave.freq; - reg24 accumulator = wave.accumulator; - - // Clock on MSB off if MSB is on, clock on MSB on if MSB is off. - reg24 delta_accumulator = - (accumulator & 0x800000 ? 0x1000000 : 0x800000) - accumulator; - - cycle_count delta_t_next = delta_accumulator/freq; - if (delta_accumulator%freq) { - ++delta_t_next; - } - - if (delta_t_next < delta_t_min) { - delta_t_min = delta_t_next; - } - } - - // Clock oscillators. - for (i = 0; i < 3; i++) { - voice[i].wave.clock(delta_t_min); - } - - // Synchronize oscillators. - for (i = 0; i < 3; i++) { - voice[i].wave.synchronize(); - } - - delta_t_osc -= delta_t_min; - } - - // Clock filter. - filter.clock(delta_t, - voice[0].output(), voice[1].output(), voice[2].output()); - - // Clock external filter. - extfilt.clock(delta_t, filter.output()); -} - - -/** - * SID clocking with audio sampling. - * Fixpoint arithmetics is used. - */ -int SID::clock(cycle_count& delta_t, short* buf, int n, int interleave) { - int s = 0; - - for (;;) { - cycle_count next_sample_offset = sample_offset + cycles_per_sample + (1 << (FIXP_SHIFT - 1)); - cycle_count delta_t_sample = next_sample_offset >> FIXP_SHIFT; - if (delta_t_sample > delta_t) { - break; - } - if (s >= n) { - return s; - } - clock(delta_t_sample); - delta_t -= delta_t_sample; - sample_offset = (next_sample_offset & FIXP_MASK) - (1 << (FIXP_SHIFT - 1)); - buf[s++*interleave] = output(); - } - - clock(delta_t); - sample_offset -= delta_t << FIXP_SHIFT; - delta_t = 0; - return s; -} - -} - -// Plugin interface -// (This can only create a null driver since C64 audio support is not part of the -// midi driver architecture. But we need the plugin for the options menu in the launcher -// and for MidiDriver::detectDevice() which is more or less used by all engines.) - -class C64MusicPlugin : public NullMusicPlugin { -public: - const char *getName() const { - return _s("C64 Audio Emulator"); - } - - const char *getId() const { - return "C64"; - } - - MusicDevices getDevices() const; -}; - -MusicDevices C64MusicPlugin::getDevices() const { - MusicDevices devices; - devices.push_back(MusicDevice(this, "", MT_C64)); - return devices; -} - -//#if PLUGIN_ENABLED_DYNAMIC(C64) - //REGISTER_PLUGIN_DYNAMIC(C64, PLUGIN_TYPE_MUSIC, C64MusicPlugin); -//#else - REGISTER_PLUGIN_STATIC(C64, PLUGIN_TYPE_MUSIC, C64MusicPlugin); -//#endif - -#endif diff --git a/sound/softsynth/sid.h b/sound/softsynth/sid.h deleted file mode 100644 index c78f538441..0000000000 --- a/sound/softsynth/sid.h +++ /dev/null @@ -1,348 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/* - * This file is based on reSID, a MOS6581 SID emulator engine. - * Copyright (C) 2004 Dag Lem - */ - -#ifndef __SID_H__ -#define __SID_H__ - -// Inlining on/off. -#define RESID_INLINE inline - -namespace Resid { - -// We could have used the smallest possible data type for each SID register, -// however this would give a slower engine because of data type conversions. -// An int is assumed to be at least 32 bits (necessary in the types reg24, -// cycle_count, and sound_sample). GNU does not support 16-bit machines -// (GNU Coding Standards: Portability between CPUs), so this should be -// a valid assumption. - -typedef unsigned int reg4; -typedef unsigned int reg8; -typedef unsigned int reg12; -typedef unsigned int reg16; -typedef unsigned int reg24; - -typedef int cycle_count; -typedef int sound_sample; -typedef sound_sample fc_point[2]; - - -class WaveformGenerator { -public: - WaveformGenerator(); - - void set_sync_source(WaveformGenerator*); - - void clock(cycle_count delta_t); - void synchronize(); - void reset(); - - void writeFREQ_LO(reg8); - void writeFREQ_HI(reg8); - void writePW_LO(reg8); - void writePW_HI(reg8); - void writeCONTROL_REG(reg8); - reg8 readOSC(); - - // 12-bit waveform output. - reg12 output(); - -protected: - const WaveformGenerator* sync_source; - WaveformGenerator* sync_dest; - - // Tell whether the accumulator MSB was set high on this cycle. - bool msb_rising; - - reg24 accumulator; - reg24 shift_register; - - // Fout = (Fn*Fclk/16777216)Hz - reg16 freq; - // PWout = (PWn/40.95)% - reg12 pw; - - // The control register right-shifted 4 bits; used for output function - // table lookup. - reg8 waveform; - - // The remaining control register bits. - reg8 test; - reg8 ring_mod; - reg8 sync; - // The gate bit is handled by the EnvelopeGenerator. - - // 16 possible combinations of waveforms. - reg12 output____(); - reg12 output___T(); - reg12 output__S_(); - reg12 output__ST(); - reg12 output_P__(); - reg12 output_P_T(); - reg12 output_PS_(); - reg12 output_PST(); - reg12 outputN___(); - reg12 outputN__T(); - reg12 outputN_S_(); - reg12 outputN_ST(); - reg12 outputNP__(); - reg12 outputNP_T(); - reg12 outputNPS_(); - reg12 outputNPST(); - - // Sample data for combinations of waveforms. - static const reg8 wave6581__ST[]; - static const reg8 wave6581_P_T[]; - static const reg8 wave6581_PS_[]; - static const reg8 wave6581_PST[]; - - friend class Voice; - friend class SID; -}; - -class Filter { -public: - Filter(); - - void enable_filter(bool enable); - - void clock(cycle_count delta_t, - sound_sample voice1, sound_sample voice2, sound_sample voice3); - void reset(); - - // Write registers. - void writeFC_LO(reg8); - void writeFC_HI(reg8); - void writeRES_FILT(reg8); - void writeMODE_VOL(reg8); - - // SID audio output (16 bits). - sound_sample output(); - -protected: - void set_w0(); - void set_Q(); - - // Filter enabled. - bool enabled; - - // Filter cutoff frequency. - reg12 fc; - - // Filter resonance. - reg8 res; - - // Selects which inputs to route through filter. - reg8 filt; - - // Switch voice 3 off. - reg8 voice3off; - - // Highpass, bandpass, and lowpass filter modes. - reg8 hp_bp_lp; - - // Output master volume. - reg4 vol; - - // Mixer DC offset. - sound_sample mixer_DC; - - // State of filter. - sound_sample Vhp; // highpass - sound_sample Vbp; // bandpass - sound_sample Vlp; // lowpass - sound_sample Vnf; // not filtered - - // Cutoff frequency, resonance. - sound_sample w0, w0_ceil_1, w0_ceil_dt; - sound_sample _1024_div_Q; - - // Cutoff frequency tables. - // FC is an 11 bit register. - sound_sample f0_6581[2048]; - sound_sample* f0; - static fc_point f0_points_6581[]; - fc_point* f0_points; - int f0_count; - - friend class SID; -}; - -class EnvelopeGenerator { -public: - EnvelopeGenerator(); - - enum State { ATTACK, DECAY_SUSTAIN, RELEASE }; - - void clock(cycle_count delta_t); - void reset(); - - void writeCONTROL_REG(reg8); - void writeATTACK_DECAY(reg8); - void writeSUSTAIN_RELEASE(reg8); - reg8 readENV(); - - // 8-bit envelope output. - reg8 output(); - -protected: - reg16 rate_counter; - reg16 rate_period; - reg8 exponential_counter; - reg8 exponential_counter_period; - reg8 envelope_counter; - bool hold_zero; - - reg4 attack; - reg4 decay; - reg4 sustain; - reg4 release; - - reg8 gate; - - State state; - - // Lookup table to convert from attack, decay, or release value to rate - // counter period. - static reg16 rate_counter_period[]; - - // The 16 selectable sustain levels. - static reg8 sustain_level[]; - - friend class SID; -}; - -class ExternalFilter { -public: - ExternalFilter(); - - void enable_filter(bool enable); - void set_sampling_parameter(double pass_freq); - - void clock(cycle_count delta_t, sound_sample Vi); - void reset(); - - // Audio output (20 bits). - sound_sample output(); - -protected: - // Filter enabled. - bool enabled; - - // Maximum mixer DC offset. - sound_sample mixer_DC; - - // State of filters. - sound_sample Vlp; // lowpass - sound_sample Vhp; // highpass - sound_sample Vo; - - // Cutoff frequencies. - sound_sample w0lp; - sound_sample w0hp; - - friend class SID; -}; - -class Voice { -public: - Voice(); - - void set_sync_source(Voice*); - void reset(); - - void writeCONTROL_REG(reg8); - - // Amplitude modulated waveform output. - // Range [-2048*255, 2047*255]. - sound_sample output() { - // Multiply oscillator output with envelope output. - return (wave.output() - wave_zero)*envelope.output() + voice_DC; - } - -protected: - WaveformGenerator wave; - EnvelopeGenerator envelope; - - // Waveform D/A zero level. - sound_sample wave_zero; - - // Multiplying D/A DC offset. - sound_sample voice_DC; - - friend class SID; -}; - - -class SID { -public: - SID(); - ~SID(); - - void enable_filter(bool enable); - void enable_external_filter(bool enable); - bool set_sampling_parameters(double clock_freq, - double sample_freq, double pass_freq = -1, - double filter_scale = 0.97); - - void clock(cycle_count delta_t); - int clock(cycle_count& delta_t, short* buf, int n, int interleave = 1); - void reset(); - - // Read/write registers. - reg8 read(reg8 offset); - void write(reg8 offset, reg8 value); - - // 16-bit output (AUDIO OUT). - int output(); - -protected: - Voice voice[3]; - Filter filter; - ExternalFilter extfilt; - - reg8 bus_value; - cycle_count bus_value_ttl; - - double clock_frequency; - - // Fixpoint constants. - static const int FIXP_SHIFT; - static const int FIXP_MASK; - - // Sampling variables. - cycle_count cycles_per_sample; - cycle_count sample_offset; - short sample_prev; -}; - -} - -#endif // not __SID_H__ diff --git a/sound/softsynth/wave6581.cpp b/sound/softsynth/wave6581.cpp deleted file mode 100644 index d1ddad1623..0000000000 --- a/sound/softsynth/wave6581.cpp +++ /dev/null @@ -1,2098 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/* - * This file is based on reSID, a MOS6581 SID emulator engine. - * Copyright (C) 2004 Dag Lem - */ - -#ifndef DISABLE_SID - -#include "sid.h" - -namespace Resid { - -const reg8 WaveformGenerator::wave6581__ST[] = { -/* 0x000: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x008: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x010: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x018: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x020: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x028: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x030: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x038: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x040: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x048: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x050: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x058: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x060: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x068: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x070: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x078: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0x080: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x088: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x090: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x098: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x0c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, -/* 0x100: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x108: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x110: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x118: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x120: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x128: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x130: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x138: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x140: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x148: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x150: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x158: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x160: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x168: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x170: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x178: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0x180: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x188: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x190: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x198: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x1c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1f8: */ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, -/* 0x200: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x208: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x210: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x218: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x220: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x228: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x230: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x238: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x240: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x248: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x250: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x258: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x260: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x268: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x270: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x278: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0x280: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x288: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x290: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x298: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x2c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, -/* 0x300: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x308: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x310: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x318: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x320: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x328: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x330: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x338: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x340: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x348: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x350: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x358: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x360: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x368: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x370: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x378: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0x380: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x388: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x390: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x398: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x3c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3f0: */ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, -/* 0x3f8: */ 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x3f, 0x3f, -/* 0x400: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x408: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x410: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x418: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x420: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x428: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x430: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x438: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x440: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x448: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x450: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x458: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x460: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x468: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x470: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x478: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0x480: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x488: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x490: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x498: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x4c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, -/* 0x500: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x508: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x510: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x518: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x520: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x528: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x530: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x538: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x540: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x548: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x550: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x558: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x560: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x568: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x570: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x578: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0x580: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x588: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x590: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x598: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x5c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5f8: */ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x1f, -/* 0x600: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x608: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x610: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x618: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x620: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x628: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x630: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x638: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x640: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x648: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x650: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x658: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x660: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x668: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x670: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x678: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0x680: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x688: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x690: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x698: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x6c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, -/* 0x700: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x708: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x710: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x718: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x720: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x728: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x730: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x738: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x740: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x748: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x750: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x758: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x760: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x768: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x770: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x778: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0x780: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x788: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x790: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x798: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x7c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7e0: */ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, -/* 0x7e8: */ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, -/* 0x7f0: */ 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, -/* 0x7f8: */ 0x3e, 0x3e, 0x3f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f, -/* 0x800: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x808: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x810: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x818: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x820: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x828: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x830: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x838: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x840: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x848: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x850: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x858: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x860: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x868: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x870: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x878: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0x880: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x888: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x890: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x898: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x8c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, -/* 0x900: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x908: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x910: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x918: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x920: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x928: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x930: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x938: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x940: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x948: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x950: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x958: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x960: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x968: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x970: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x978: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0x980: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x988: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x990: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x998: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x9c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9f8: */ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, -/* 0xa00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0xa80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xab0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xab8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0xac0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xac8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xad0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xad8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xae0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xae8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaf8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, -/* 0xb00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0xb40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0xb80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xba0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xba8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0xbc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbf0: */ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, -/* 0xbf8: */ 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x3f, 0x3f, -/* 0xc00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0xc80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xca0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xca8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0xcc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xce0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xce8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcf8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, -/* 0xd00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0xd40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0xd80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xda0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xda8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0xdc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xde0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xde8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdf8: */ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x1f, -/* 0xe00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0xe80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xea0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xea8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xeb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xeb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0xec0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xec8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xed0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xed8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xee0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xee8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xef0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xef8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, -/* 0xf00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0xf40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, -/* 0xf80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0xfc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfe0: */ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, -/* 0xfe8: */ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, -/* 0xff0: */ 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, -/* 0xff8: */ 0x3e, 0x3e, 0x3f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f, -}; - -const reg8 WaveformGenerator::wave6581_P_T[] = { -/* 0x000: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x008: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x010: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x018: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x020: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x028: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x030: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x038: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x040: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x048: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x050: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x058: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x060: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x068: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x070: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x078: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x080: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x088: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x090: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x098: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x100: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x108: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x110: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x118: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x120: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x128: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x130: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x138: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x140: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x148: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x150: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x158: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x160: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x168: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x170: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x178: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x180: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x188: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x190: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x198: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x38, 0x3f, -/* 0x200: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x208: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x210: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x218: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x220: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x228: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x230: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x238: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x240: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x248: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x250: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x258: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x260: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x268: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x270: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x278: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x280: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x288: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x290: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x298: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2f8: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x5f, -/* 0x300: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x308: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x310: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x318: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x320: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x328: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x330: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x338: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x340: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x348: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x350: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x358: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x360: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x368: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0x370: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0x378: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x6f, -/* 0x380: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x388: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x390: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x398: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0x3a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, -/* 0x3b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, -/* 0x3b8: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x70, 0x77, -/* 0x3c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, -/* 0x3d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, -/* 0x3d8: */ 0x00, 0x00, 0x00, 0x70, 0x40, 0x70, 0x70, 0x7b, -/* 0x3e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x70, -/* 0x3e8: */ 0x00, 0x40, 0x40, 0x70, 0x60, 0x70, 0x78, 0x7d, -/* 0x3f0: */ 0x00, 0x40, 0x60, 0x78, 0x60, 0x78, 0x78, 0x7e, -/* 0x3f8: */ 0x70, 0x7c, 0x7c, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, -/* 0x400: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x408: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x410: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x418: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x420: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x428: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x430: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x438: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x440: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x448: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x450: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x458: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x460: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x468: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x470: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x478: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x480: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x488: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x490: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x498: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x4c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x4e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x4f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x4f8: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x9f, -/* 0x500: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x508: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x510: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x518: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x520: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x528: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x530: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x538: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x540: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x548: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x550: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x558: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x560: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x568: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x570: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x578: */ 0x00, 0x80, 0x80, 0x80, 0x80, 0xa0, 0xa0, 0xaf, -/* 0x580: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x588: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x590: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x598: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x5a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, -/* 0x5b0: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xa0, -/* 0x5b8: */ 0x00, 0x80, 0x80, 0xa0, 0x80, 0xa0, 0xb0, 0xb7, -/* 0x5c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x5c8: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xa0, -/* 0x5d0: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xa0, -/* 0x5d8: */ 0x00, 0x80, 0x80, 0xa0, 0x80, 0xb0, 0xb0, 0xbb, -/* 0x5e0: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0xb0, -/* 0x5e8: */ 0x80, 0x80, 0x80, 0xb0, 0x80, 0xb0, 0xb8, 0xbd, -/* 0x5f0: */ 0x80, 0x80, 0x80, 0xb8, 0xa0, 0xb8, 0xb8, 0xbe, -/* 0x5f8: */ 0xa0, 0xb8, 0xbc, 0xbf, 0xbe, 0xbf, 0xbf, 0xbf, -/* 0x600: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x608: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x610: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x618: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x620: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x628: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x630: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x638: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, -/* 0x640: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x648: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x650: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x658: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0xc0, -/* 0x660: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x668: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, -/* 0x670: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, -/* 0x678: */ 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xcf, -/* 0x680: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x688: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x690: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x698: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, -/* 0x6a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x6a8: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, -/* 0x6b0: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0xc0, 0xc0, -/* 0x6b8: */ 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xd0, 0xd7, -/* 0x6c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x6c8: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0xc0, 0xc0, -/* 0x6d0: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xc0, -/* 0x6d8: */ 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xd0, 0xd0, 0xdb, -/* 0x6e0: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xd0, -/* 0x6e8: */ 0x80, 0xc0, 0xc0, 0xd0, 0xc0, 0xd0, 0xd8, 0xdd, -/* 0x6f0: */ 0xc0, 0xc0, 0xc0, 0xd0, 0xc0, 0xd8, 0xd8, 0xde, -/* 0x6f8: */ 0xc0, 0xd8, 0xdc, 0xdf, 0xdc, 0xdf, 0xdf, 0xdf, -/* 0x700: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x708: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x710: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x718: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xe0, -/* 0x720: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -/* 0x728: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xe0, -/* 0x730: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xe0, -/* 0x738: */ 0x80, 0xc0, 0xc0, 0xe0, 0xc0, 0xe0, 0xe0, 0xe7, -/* 0x740: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, -/* 0x748: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xe0, -/* 0x750: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xe0, -/* 0x758: */ 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xeb, -/* 0x760: */ 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, -/* 0x768: */ 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xed, -/* 0x770: */ 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe8, 0xe8, 0xee, -/* 0x778: */ 0xe0, 0xe8, 0xec, 0xef, 0xec, 0xef, 0xef, 0xef, -/* 0x780: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0xc0, -/* 0x788: */ 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xf0, -/* 0x790: */ 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xf0, -/* 0x798: */ 0xc0, 0xe0, 0xe0, 0xf0, 0xe0, 0xf0, 0xf0, 0xf3, -/* 0x7a0: */ 0x80, 0xc0, 0xc0, 0xe0, 0xc0, 0xe0, 0xe0, 0xf0, -/* 0x7a8: */ 0xc0, 0xe0, 0xe0, 0xf0, 0xe0, 0xf0, 0xf0, 0xf5, -/* 0x7b0: */ 0xe0, 0xe0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf6, -/* 0x7b8: */ 0xf0, 0xf0, 0xf4, 0xf7, 0xf4, 0xf7, 0xf7, 0xf7, -/* 0x7c0: */ 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xf0, -/* 0x7c8: */ 0xe0, 0xe0, 0xe0, 0xf8, 0xf0, 0xf8, 0xf8, 0xf9, -/* 0x7d0: */ 0xe0, 0xf0, 0xf0, 0xf8, 0xf0, 0xf8, 0xf8, 0xfa, -/* 0x7d8: */ 0xf0, 0xf8, 0xf8, 0xfb, 0xf8, 0xfb, 0xfb, 0xfb, -/* 0x7e0: */ 0xe0, 0xf0, 0xf0, 0xf8, 0xf0, 0xf8, 0xfc, 0xfc, -/* 0x7e8: */ 0xf8, 0xfc, 0xfc, 0xfd, 0xfc, 0xfd, 0xfd, 0xfd, -/* 0x7f0: */ 0xf8, 0xfc, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, -/* 0x7f8: */ 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -/* 0x800: */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, -/* 0x808: */ 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfc, 0xf8, -/* 0x810: */ 0xfd, 0xfd, 0xfd, 0xfc, 0xfd, 0xfc, 0xfc, 0xf8, -/* 0x818: */ 0xfc, 0xfc, 0xfc, 0xf0, 0xf8, 0xf0, 0xf0, 0xe0, -/* 0x820: */ 0xfb, 0xfb, 0xfb, 0xf8, 0xfb, 0xf8, 0xf8, 0xf0, -/* 0x828: */ 0xfa, 0xf8, 0xf8, 0xf0, 0xf8, 0xf0, 0xf0, 0xe0, -/* 0x830: */ 0xf9, 0xf8, 0xf8, 0xf0, 0xf8, 0xf0, 0xe0, 0xe0, -/* 0x838: */ 0xf0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, -/* 0x840: */ 0xf7, 0xf7, 0xf7, 0xf4, 0xf7, 0xf4, 0xf0, 0xf0, -/* 0x848: */ 0xf6, 0xf0, 0xf0, 0xf0, 0xf0, 0xe0, 0xe0, 0xe0, -/* 0x850: */ 0xf5, 0xf0, 0xf0, 0xe0, 0xf0, 0xe0, 0xe0, 0xc0, -/* 0x858: */ 0xf0, 0xe0, 0xe0, 0xc0, 0xe0, 0xc0, 0xc0, 0x80, -/* 0x860: */ 0xf3, 0xf0, 0xf0, 0xe0, 0xf0, 0xe0, 0xe0, 0xc0, -/* 0x868: */ 0xf0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, -/* 0x870: */ 0xf0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, 0x80, -/* 0x878: */ 0xc0, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, -/* 0x880: */ 0xef, 0xef, 0xef, 0xec, 0xef, 0xec, 0xe8, 0xe0, -/* 0x888: */ 0xee, 0xe8, 0xe8, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, -/* 0x890: */ 0xed, 0xe8, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, -/* 0x898: */ 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, 0x80, -/* 0x8a0: */ 0xeb, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, -/* 0x8a8: */ 0xe0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00, -/* 0x8b0: */ 0xe0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00, -/* 0x8b8: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, -/* 0x8c0: */ 0xe7, 0xe0, 0xe0, 0xc0, 0xe0, 0xc0, 0xc0, 0x80, -/* 0x8c8: */ 0xe0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00, -/* 0x8d0: */ 0xe0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00, -/* 0x8d8: */ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8e0: */ 0xe0, 0xc0, 0xc0, 0x80, 0x80, 0x00, 0x00, 0x00, -/* 0x8e8: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8f0: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x900: */ 0xdf, 0xdf, 0xdf, 0xdc, 0xdf, 0xdc, 0xd8, 0xc0, -/* 0x908: */ 0xde, 0xd8, 0xd8, 0xc0, 0xd8, 0xc0, 0xc0, 0xc0, -/* 0x910: */ 0xdd, 0xd8, 0xd0, 0xc0, 0xd0, 0xc0, 0xc0, 0x80, -/* 0x918: */ 0xd0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00, -/* 0x920: */ 0xdb, 0xd0, 0xd0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, -/* 0x928: */ 0xc0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00, -/* 0x930: */ 0xc0, 0xc0, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, -/* 0x938: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x940: */ 0xd7, 0xd0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, -/* 0x948: */ 0xc0, 0xc0, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, -/* 0x950: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, -/* 0x958: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x960: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, -/* 0x968: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x970: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x978: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x980: */ 0xcf, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, 0x80, 0x00, -/* 0x988: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, -/* 0x990: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, -/* 0x998: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9a0: */ 0xc0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9c0: */ 0xc0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa00: */ 0xbf, 0xbf, 0xbf, 0xbe, 0xbf, 0xbc, 0xbc, 0xa0, -/* 0xa08: */ 0xbe, 0xbc, 0xb8, 0xa0, 0xb8, 0xa0, 0x80, 0x80, -/* 0xa10: */ 0xbd, 0xb8, 0xb0, 0x80, 0xb0, 0x80, 0x80, 0x80, -/* 0xa18: */ 0xb0, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, -/* 0xa20: */ 0xbb, 0xb0, 0xb0, 0x80, 0xa0, 0x80, 0x80, 0x00, -/* 0xa28: */ 0xa0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, -/* 0xa30: */ 0xa0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, -/* 0xa38: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa40: */ 0xb7, 0xb0, 0xa0, 0x80, 0xa0, 0x80, 0x80, 0x00, -/* 0xa48: */ 0xa0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, -/* 0xa50: */ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa60: */ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa80: */ 0xaf, 0xa0, 0xa0, 0x80, 0x80, 0x80, 0x80, 0x00, -/* 0xa88: */ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa90: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaa0: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xab0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xab8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xac0: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xac8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xad0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xad8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xae0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xae8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb00: */ 0x9f, 0x90, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, -/* 0xb08: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb10: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb20: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb40: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb80: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xba0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xba8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc00: */ 0x7f, 0x7f, 0x7f, 0x7e, 0x7f, 0x7c, 0x7c, 0x70, -/* 0xc08: */ 0x7e, 0x7c, 0x78, 0x60, 0x78, 0x60, 0x60, 0x00, -/* 0xc10: */ 0x7d, 0x78, 0x78, 0x60, 0x70, 0x40, 0x40, 0x00, -/* 0xc18: */ 0x70, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc20: */ 0x7b, 0x78, 0x70, 0x40, 0x70, 0x40, 0x00, 0x00, -/* 0xc28: */ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc30: */ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc40: */ 0x77, 0x70, 0x70, 0x00, 0x60, 0x00, 0x00, 0x00, -/* 0xc48: */ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc50: */ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc60: */ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc80: */ 0x6f, 0x60, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, -/* 0xc88: */ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc90: */ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xca0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xca8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xce0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xce8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd00: */ 0x5f, 0x58, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, -/* 0xd08: */ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xda0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xda8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xde0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xde8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe00: */ 0x3f, 0x3c, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xea0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xea8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xeb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xeb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xec0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xec8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xed0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xed8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xee0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xee8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xef0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xef8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xff0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xff8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -const reg8 WaveformGenerator::wave6581_PS_[] = { -/* 0x000: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x008: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x010: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x018: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x020: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x028: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x030: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x038: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x040: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x048: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x050: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x058: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x060: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x068: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x070: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x078: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x080: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x088: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x090: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x098: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, -/* 0x100: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x108: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x110: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x118: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x120: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x128: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x130: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x138: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x140: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x148: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x150: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x158: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x160: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x168: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x170: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x178: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, -/* 0x180: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x188: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x190: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x198: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, -/* 0x1c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1f, -/* 0x200: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x208: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x210: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x218: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x220: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x228: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x230: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x238: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x240: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x248: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x250: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x258: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x260: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x268: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x270: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x278: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, -/* 0x280: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x288: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x290: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x298: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0x2c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, -/* 0x300: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x308: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x310: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x318: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x320: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x328: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x330: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x338: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x340: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x348: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x350: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x358: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x360: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x368: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x370: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x378: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, -/* 0x380: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x388: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x390: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x398: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, -/* 0x3c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, -/* 0x3e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, -/* 0x3f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x3f, -/* 0x3f8: */ 0x00, 0x30, 0x38, 0x3f, 0x3e, 0x3f, 0x3f, 0x3f, -/* 0x400: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x408: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x410: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x418: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x420: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x428: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x430: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x438: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x440: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x448: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x450: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x458: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x460: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x468: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x470: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x478: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, -/* 0x480: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x488: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x490: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x498: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, -/* 0x500: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x508: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x510: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x518: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x520: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x528: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x530: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x538: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x540: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x548: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x550: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x558: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x560: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x568: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x570: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x578: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, -/* 0x580: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x588: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x590: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x598: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, -/* 0x5c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, -/* 0x5e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, -/* 0x5f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x5f, -/* 0x5f8: */ 0x00, 0x40, 0x40, 0x5f, 0x5c, 0x5f, 0x5f, 0x5f, -/* 0x600: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x608: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x610: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x618: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x620: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x628: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x630: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x638: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x640: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x648: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x650: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x658: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x660: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x668: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x670: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x678: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, -/* 0x680: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x688: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x690: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x698: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6b, -/* 0x6c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x6d, -/* 0x6e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0x6e8: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x6e, -/* 0x6f0: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x60, 0x60, 0x6f, -/* 0x6f8: */ 0x00, 0x60, 0x60, 0x6f, 0x60, 0x6f, 0x6f, 0x6f, -/* 0x700: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x708: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x710: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x718: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0x720: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x728: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0x730: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0x738: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x60, 0x73, -/* 0x740: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x748: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0x750: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0x758: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x60, 0x60, 0x75, -/* 0x760: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, -/* 0x768: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x76, -/* 0x770: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x77, -/* 0x778: */ 0x00, 0x70, 0x70, 0x77, 0x70, 0x77, 0x77, 0x77, -/* 0x780: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x788: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, -/* 0x790: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, -/* 0x798: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x79, -/* 0x7a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, -/* 0x7a8: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x70, 0x70, 0x7a, -/* 0x7b0: */ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x70, 0x7b, -/* 0x7b8: */ 0x40, 0x70, 0x70, 0x7b, 0x78, 0x7b, 0x7b, 0x7b, -/* 0x7c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, -/* 0x7c8: */ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x70, 0x7c, -/* 0x7d0: */ 0x00, 0x00, 0x00, 0x70, 0x40, 0x70, 0x70, 0x7d, -/* 0x7d8: */ 0x40, 0x70, 0x78, 0x7d, 0x78, 0x7d, 0x7d, 0x7d, -/* 0x7e0: */ 0x00, 0x40, 0x40, 0x78, 0x60, 0x78, 0x78, 0x7e, -/* 0x7e8: */ 0x60, 0x78, 0x78, 0x7e, 0x7c, 0x7e, 0x7e, 0x7e, -/* 0x7f0: */ 0x70, 0x7c, 0x7c, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, -/* 0x7f8: */ 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, -/* 0x800: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x808: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x810: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x818: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x820: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x828: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x830: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x838: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x840: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x848: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x850: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x858: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x860: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x868: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x870: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x878: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x880: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x888: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x890: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x898: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, -/* 0x900: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x908: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x910: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x918: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x920: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x928: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x930: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x938: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x940: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x948: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x950: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x958: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x960: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x968: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x970: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x978: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, -/* 0x980: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x988: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x990: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x998: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, -/* 0x9c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1f, -/* 0xa00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, -/* 0xa80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xab0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xab8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -/* 0xac0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xac8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xad0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xad8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xae0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xae8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, -/* 0xb00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, -/* 0xb80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xba0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xba8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, -/* 0xbc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, -/* 0xbe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, -/* 0xbf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x3f, -/* 0xbf8: */ 0x00, 0x30, 0x38, 0x3f, 0x3e, 0x3f, 0x3f, 0x3f, -/* 0xc00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, -/* 0xc80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xca0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xca8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xce0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xce8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, -/* 0xd00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, -/* 0xd80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xda0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xda8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, -/* 0xdc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, -/* 0xde0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xde8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, -/* 0xdf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x5f, -/* 0xdf8: */ 0x00, 0x40, 0x40, 0x5f, 0x5c, 0x5f, 0x5f, 0x5f, -/* 0xe00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, -/* 0xe80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xea0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xea8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xeb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xeb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6b, -/* 0xec0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xec8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xed0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xed8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6d, -/* 0xee0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0xee8: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x6e, -/* 0xef0: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x60, 0x60, 0x6f, -/* 0xef8: */ 0x00, 0x60, 0x60, 0x6f, 0x60, 0x6f, 0x6f, 0x6f, -/* 0xf00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0xf20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0xf30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0xf38: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x60, 0x73, -/* 0xf40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0xf50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, -/* 0xf58: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x60, 0x60, 0x75, -/* 0xf60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, -/* 0xf68: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x76, -/* 0xf70: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x77, -/* 0xf78: */ 0x00, 0x70, 0x70, 0x77, 0x70, 0x77, 0x77, 0x77, -/* 0xf80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, -/* 0xf90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, -/* 0xf98: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x79, -/* 0xfa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, -/* 0xfa8: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x70, 0x70, 0x7a, -/* 0xfb0: */ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x70, 0x7b, -/* 0xfb8: */ 0x40, 0x70, 0x70, 0x7b, 0x78, 0x7b, 0x7b, 0x7b, -/* 0xfc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, -/* 0xfc8: */ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x70, 0x7c, -/* 0xfd0: */ 0x00, 0x00, 0x00, 0x70, 0x40, 0x70, 0x70, 0x7d, -/* 0xfd8: */ 0x40, 0x70, 0x78, 0x7d, 0x78, 0x7d, 0x7d, 0x7d, -/* 0xfe0: */ 0x00, 0x40, 0x40, 0x78, 0x60, 0x78, 0x78, 0x7e, -/* 0xfe8: */ 0x60, 0x78, 0x78, 0x7e, 0x7c, 0x7e, 0x7e, 0x7e, -/* 0xff0: */ 0x70, 0x7c, 0x7c, 0x7f, 0x7c, 0x7f, 0x7f, 0x7f, -/* 0xff8: */ 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, -}; - -const reg8 WaveformGenerator::wave6581_PST[] = { -/* 0x000: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x008: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x010: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x018: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x020: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x028: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x030: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x038: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x040: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x048: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x050: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x058: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x060: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x068: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x070: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x078: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x080: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x088: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x090: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x098: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x0f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x100: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x108: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x110: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x118: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x120: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x128: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x130: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x138: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x140: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x148: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x150: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x158: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x160: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x168: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x170: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x178: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x180: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x188: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x190: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x198: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x1f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x200: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x208: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x210: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x218: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x220: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x228: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x230: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x238: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x240: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x248: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x250: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x258: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x260: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x268: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x270: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x278: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x280: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x288: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x290: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x298: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x2f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x300: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x308: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x310: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x318: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x320: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x328: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x330: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x338: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x340: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x348: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x350: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x358: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x360: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x368: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x370: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x378: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x380: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x388: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x390: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x398: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x3f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, -/* 0x400: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x408: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x410: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x418: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x420: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x428: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x430: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x438: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x440: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x448: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x450: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x458: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x460: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x468: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x470: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x478: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x480: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x488: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x490: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x498: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x4f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x500: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x508: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x510: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x518: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x520: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x528: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x530: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x538: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x540: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x548: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x550: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x558: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x560: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x568: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x570: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x578: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x580: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x588: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x590: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x598: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x5f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x600: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x608: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x610: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x618: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x620: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x628: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x630: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x638: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x640: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x648: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x650: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x658: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x660: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x668: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x670: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x678: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x680: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x688: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x690: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x698: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x6f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x700: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x708: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x710: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x718: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x720: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x728: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x730: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x738: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x740: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x748: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x750: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x758: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x760: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x768: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x770: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x778: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x780: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x788: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x790: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x798: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x7e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, -/* 0x7f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, -/* 0x7f8: */ 0x00, 0x00, 0x00, 0x78, 0x78, 0x7e, 0x7f, 0x7f, -/* 0x800: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x808: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x810: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x818: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x820: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x828: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x830: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x838: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x840: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x848: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x850: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x858: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x860: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x868: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x870: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x878: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x880: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x888: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x890: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x898: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x8f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x900: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x908: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x910: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x918: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x920: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x928: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x930: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x938: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x940: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x948: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x950: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x958: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x960: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x968: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x970: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x978: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x980: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x988: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x990: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x998: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0x9f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xa98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xab0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xab8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xac0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xac8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xad0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xad8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xae0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xae8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xaf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xb98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xba0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xba8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xbf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, -/* 0xc00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xc98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xca0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xca8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xce0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xce8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xcf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xd98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xda0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xda8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xde0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xde8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xdf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xe98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xea0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xea8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xeb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xeb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xec0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xec8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xed0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xed8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xee0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xee8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xef0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xef8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xf98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -/* 0xfe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, -/* 0xff0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, -/* 0xff8: */ 0x00, 0x00, 0x00, 0x78, 0x78, 0x7e, 0x7f, 0x7f, -}; - -} -#endif diff --git a/sound/softsynth/ym2612.cpp b/sound/softsynth/ym2612.cpp deleted file mode 100644 index 08331c6244..0000000000 --- a/sound/softsynth/ym2612.cpp +++ /dev/null @@ -1,789 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#include - -#include "sound/softsynth/ym2612.h" -#include "common/util.h" -#include "sound/musicplugin.h" -#include "common/translation.h" - -//////////////////////////////////////// -// -// Miscellaneous -// -//////////////////////////////////////// - -static int *sintbl = 0; -static int *powtbl = 0; -static int *frequencyTable = 0; -static int *keycodeTable = 0; -static int *keyscaleTable = 0; -static int *attackOut = 0; - - -//////////////////////////////////////// -// -// Operator2612 implementation -// -//////////////////////////////////////// - -Operator2612::Operator2612 (Voice2612 *owner) : - _owner (owner), - _state (_s_ready), - _currentLevel ((int32)0x7f << 15), - _phase (0), - _lastOutput (0), - _feedbackLevel (0), - _detune (0), - _multiple (1), - _keyScale (0), - _specifiedTotalLevel (127), - _specifiedAttackRate (0), - _specifiedDecayRate (0), - _specifiedSustainRate (0), - _specifiedReleaseRate (15) { - velocity(0); -} - -Operator2612::~Operator2612() -{ } - -void Operator2612::velocity(int velo) { - _velocity = velo; - _totalLevel = ((int32)_specifiedTotalLevel << 15) + - ((int32)(127-_velocity) << 13); - _sustainLevel = ((int32)_specifiedSustainLevel << 17); -} - -void Operator2612::feedbackLevel(int level) { - _feedbackLevel = level; -} - -void Operator2612::setInstrument(byte const *instrument) { - _detune = (instrument[8] >> 4) & 7; - _multiple = instrument[8] & 15; - _specifiedTotalLevel = instrument[12] & 127; - _keyScale = (instrument[16] >> 6) & 3; - _specifiedAttackRate = instrument[16] & 31; - _specifiedDecayRate = instrument[20] & 31; - _specifiedSustainRate = instrument[24] & 31; - _specifiedSustainLevel = (instrument[28] >> 4) & 15; - _specifiedReleaseRate = instrument[28] & 15; - _state = _s_ready; - velocity(_velocity); -} - -void Operator2612::keyOn() { - _state = _s_attacking; - _tickCount = 0; - _phase = 0; - _currentLevel = ((int32)0x7f << 15); -} - -void Operator2612::keyOff() { - if (_state != _s_ready) - _state = _s_releasing; -} - -void Operator2612::frequency(int freq) { - double value; // Use for intermediate computations to avoid int64 arithmetic - int r; - - _frequency = freq / _owner->_rate; - - r = _specifiedAttackRate; - if (r != 0) { - r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale)); - if (r >= 64) - r = 63; - } - - r = 63 - r; - if (_specifiedTotalLevel >= 128) - value = 0; - else { - value = powtbl[(r&3) << 7]; - value *= 1 << (r >> 2); - value *= 41; - value /= 1 << (15 + 5); - value *= 127 - _specifiedTotalLevel; - value /= 127; - } - _attackTime = (int32) value; // 1 ?? == (1 << 12) - if (_attackTime > 0) - _attackTime = (1 << (12+10)) / (_owner->_rate * _attackTime); - - r = _specifiedDecayRate; - if (r != 0) { - r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale)); - if (r >= 64) - r = 63; - } - value = (double) powtbl[(r&3) << 7] * (0x10 << (r>>2)) / 31; - _decayRate = (int32) value / _owner->_rate; - - r = _specifiedSustainRate; - if (r != 0) { - r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale)); - if (r >= 64) - r = 63; - } - value = (double) powtbl[(r&3) << 7] * (0x10 << (r>>2)) / 31; - _sustainRate = (int32) value / _owner->_rate; - - r = _specifiedReleaseRate; - if (r != 0) { - r = r * 2 + 1; // (Translated) I cannot know whether the timing is a good choice or not - r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale)); - // KS - if (r >= 64) - r = 63; - } - value = (double) powtbl[(r&3) << 7] * (0x10 << (r>>2)) / 31; - _releaseRate = (int32) value / _owner->_rate; -} - -void Operator2612::nextTick(const int *phasebuf, int *outbuf, int buflen) { - if (_state == _s_ready) - return; - if (_state == _s_attacking && _attackTime <= 0) { - _currentLevel = 0; - _state = _s_decaying; - } - - int32 levelIncrement = 0; - int32 target = 0; - State next_state = _s_ready; - const int32 zero_level = ((int32)0x7f << 15); - const int phaseIncrement = (_multiple > 0) ? (_frequency * _multiple) : (_frequency / 2); - - int32 output = _lastOutput; - int32 level = _currentLevel + _totalLevel; - - while (buflen) { - switch (_state) { - case _s_ready: - return; - case _s_attacking: - next_state = _s_attacking; - break; - case _s_decaying: - levelIncrement = _decayRate; - target = _sustainLevel + _totalLevel; - next_state = _s_sustaining; - break; - case _s_sustaining: - levelIncrement = _sustainRate; - target = zero_level + _totalLevel; - next_state = _s_ready; - break; - case _s_releasing: - levelIncrement = _releaseRate; - target = zero_level + _totalLevel; - next_state = _s_ready; - break; - } - - bool switching = false; - do { - if (next_state == _s_attacking) { - // Attack phase - ++_tickCount; - int i = (int) (_tickCount * _attackTime); - if (i >= 1024) { - level = _totalLevel; - _state = _s_decaying; - switching = true; - } else { - level = (attackOut[i] << (31 - 8 - 16)) + _totalLevel; - } - } else { - // Decay, Sustain and Release phases - level += levelIncrement; - if (level >= target) { - level = target; - _state = next_state; - switching = true; - } - } - - if (level < zero_level) { - int phaseShift = *phasebuf >> 2; - if (_feedbackLevel) - phaseShift += (output << (_feedbackLevel - 1)) / 1024; - output = sintbl[((_phase >> 7) + phaseShift) & 0x7ff]; - output >>= (level >> 18); - // Here is the original code, which requires 64-bit ints -// output *= powtbl[511 - ((level>>25)&511)]; -// output >>= 16; -// output >>= 1; - // And here's our 32-bit trick for doing it. (Props to Fingolfin!) - // Result varies from original code by max of 1. -// int powVal = powtbl[511 - ((level>>9)&511)]; -// int outputHI = output / 256; -// int powHI = powVal / 256; -// output = (outputHI * powHI) / 2 + (outputHI * (powVal % 256) + powHI * (output % 256)) / 512; - // And here's the even faster code. - // Result varies from original code by max of 8. - output = ((output >> 4) * (powtbl[511-((level>>9)&511)] >> 3)) / 1024; - - _phase += phaseIncrement; - _phase &= 0x3ffff; - } else - output = 0; - - *outbuf += output; - --buflen; - ++phasebuf; - ++outbuf; - } while (buflen && !switching); - } - _lastOutput = output; - _currentLevel = level - _totalLevel; -} - -//////////////////////////////////////// -// -// Voice2612 implementation -// -//////////////////////////////////////// - -Voice2612::Voice2612() { - next = 0; - _control7 = 127; - _note = 40; - _frequency = 440; - _frequencyOffs = 0x2000; - _algorithm = 7; - - _buffer = 0; - _buflen = 0; - - int i; - for (i = 0; i < ARRAYSIZE(_opr); ++i) - _opr[i] = new Operator2612 (this); - velocity(0); -} - -Voice2612::~Voice2612() { - int i; - for (i = 0; i < ARRAYSIZE(_opr); ++i) - delete _opr[i]; - free(_buffer); -} - -void Voice2612::velocity(int velo) { - _velocity = velo; -#if 0 - int v = (velo * _control7) >> 7; -#else - int v = velo + (_control7 - 127) * 4; -#endif - bool iscarrier[8][4] = { - { false, false, false, true, }, //0 - { false, false, false, true, }, //1 - { false, false, false, true, }, //2 - { false, false, false, true, }, //3 - { false, true, false, true, }, //4 - { false, true, true, true, }, //5 - { false, true, true, true, }, //6 - { true, true, true, true, }, //7 - }; - int opr; - for (opr = 0; opr < 4; opr++) - if (iscarrier[_algorithm][opr]) - _opr[opr]->velocity(v); - else - _opr[opr]->velocity(127); -} - -void Voice2612::setControlParameter(int control, int value) { - switch (control) { - case 7: - _control7 = value; - velocity(_velocity); - break; - case 123: - // All notes off - noteOff(_note); - }; -} - -void Voice2612::setInstrument(byte const *instrument) { - if (instrument == NULL) - return; - - _algorithm = instrument[32] & 7; - _opr[0]->feedbackLevel((instrument[32] >> 3) & 7); - _opr[1]->feedbackLevel(0); - _opr[2]->feedbackLevel(0); - _opr[3]->feedbackLevel(0); - _opr[0]->setInstrument(instrument + 0); - _opr[1]->setInstrument(instrument + 2); - _opr[2]->setInstrument(instrument + 1); - _opr[3]->setInstrument(instrument + 3); -} - -void Voice2612::nextTick(int *outbuf, int buflen) { - if (_velocity == 0) - return; - - if (_buflen < buflen) { - free(_buffer); - _buflen = buflen; - _buffer = (int *) malloc(sizeof(int) * buflen * 2); - } - - int *buf1 = _buffer; - int *buf2 = _buffer + buflen; - memset(_buffer, 0, sizeof(int) * buflen * 2); - - switch (_algorithm) { - case 0: - _opr[0]->nextTick(buf1, buf2, buflen); - _opr[1]->nextTick(buf2, buf1, buflen); - memset (buf2, 0, sizeof (int) * buflen); - _opr[2]->nextTick(buf1, buf2, buflen); - _opr[3]->nextTick(buf2, outbuf, buflen); - break; - case 1: - _opr[0]->nextTick(buf1, buf2, buflen); - _opr[1]->nextTick(buf1, buf2, buflen); - _opr[2]->nextTick(buf2, buf1, buflen); - _opr[3]->nextTick(buf1, outbuf, buflen); - break; - case 2: - _opr[1]->nextTick(buf1, buf2, buflen); - _opr[2]->nextTick(buf2, buf1, buflen); - memset(buf2, 0, sizeof(int) * buflen); - _opr[0]->nextTick(buf2, buf1, buflen); - _opr[3]->nextTick(buf1, outbuf, buflen); - break; - case 3: - _opr[0]->nextTick(buf1, buf2, buflen); - _opr[1]->nextTick(buf2, buf1, buflen); - memset(buf2, 0, sizeof(int) * buflen); - _opr[2]->nextTick(buf2, buf1, buflen); - _opr[3]->nextTick(buf1, outbuf, buflen); - break; - case 4: - _opr[0]->nextTick(buf1, buf2, buflen); - _opr[1]->nextTick(buf2, outbuf, buflen); - _opr[2]->nextTick(buf1, buf1, buflen); - _opr[3]->nextTick(buf1, outbuf, buflen); - break; - case 5: - _opr[0]->nextTick(buf1, buf2, buflen); - _opr[1]->nextTick(buf2, outbuf, buflen); - _opr[2]->nextTick(buf2, outbuf, buflen); - _opr[3]->nextTick(buf2, outbuf, buflen); - break; - case 6: - _opr[0]->nextTick(buf1, buf2, buflen); - _opr[1]->nextTick(buf2, outbuf, buflen); - _opr[2]->nextTick(buf1, outbuf, buflen); - _opr[3]->nextTick(buf1, outbuf, buflen); - break; - case 7: - _opr[0]->nextTick(buf1, outbuf, buflen); - _opr[1]->nextTick(buf1, outbuf, buflen); - _opr[2]->nextTick(buf1, outbuf, buflen); - _opr[3]->nextTick(buf1, outbuf, buflen); - break; - }; -} - -void Voice2612::noteOn(int n, int onVelo) { - _note = n; - velocity(onVelo); - recalculateFrequency(); - int i; - for (i = 0; i < ARRAYSIZE(_opr); i++) - _opr[i]->keyOn(); -} - -bool Voice2612::noteOff(int note) { - if (_note != note) - return false; - int i; - for (i = 0; i < ARRAYSIZE(_opr); i++) - _opr[i]->keyOff(); - return true; -} - -void Voice2612::pitchBend(int value) { - _frequencyOffs = value; - recalculateFrequency(); -} - -void Voice2612::recalculateFrequency() { - // - // - // - int32 basefreq = frequencyTable[_note]; - int cfreq = frequencyTable[_note - (_note % 12)]; - int oct = _note / 12; - int fnum = (int) (((double)basefreq * (1 << 13)) / cfreq); - fnum += _frequencyOffs - 0x2000; - if (fnum < 0x2000) { - fnum += 0x2000; - oct--; - } - if (fnum >= 0x4000) { - fnum -= 0x2000; - oct++; - } - - // - _frequency = (int) ((frequencyTable[oct*12] * (double)fnum) / 8); - - int i; - for (i = 0; i < ARRAYSIZE(_opr); i++) - _opr[i]->frequency(_frequency); -} - -//////////////////////////////////////// -// -// MidiChannel_YM2612 -// -//////////////////////////////////////// - -MidiChannel_YM2612::MidiChannel_YM2612() { - _voices = 0; - _next_voice = 0; -} - -MidiChannel_YM2612::~MidiChannel_YM2612() { - removeAllVoices(); -} - -void MidiChannel_YM2612::removeAllVoices() { - if (!_voices) - return; - Voice2612 *last, *voice = _voices; - for (; voice; voice = last) { - last = voice->next; - delete voice; - } - _voices = _next_voice = 0; -} - -void MidiChannel_YM2612::noteOn(byte note, byte onVelo) { - if (!_voices) - return; - _next_voice = _next_voice ? _next_voice : _voices; - _next_voice->noteOn(note, onVelo); - _next_voice = _next_voice->next; -} - -void MidiChannel_YM2612::noteOff(byte note) { - if (!_voices) - return; - if (_next_voice == _voices) - _next_voice = 0; - Voice2612 *voice = _next_voice; - do { - if (!voice) - voice = _voices; - if (voice->noteOff(note)) { - _next_voice = voice; - break; - } - voice = voice->next; - } while (voice != _next_voice); -} - -void MidiChannel_YM2612::controlChange(byte control, byte value) { - // - if (control == 121) { - // Reset controller - removeAllVoices(); - } else { - Voice2612 *voice = _voices; - for (; voice; voice = voice->next) - voice->setControlParameter(control, value); - } -} - -void MidiChannel_YM2612::sysEx_customInstrument(uint32 type, const byte *fmInst) { - if (type != 'EUP ') - return; - Voice2612 *voice = new Voice2612; - voice->next = _voices; - _voices = voice; - voice->_rate = _rate; - voice->setInstrument(fmInst); -} - -void MidiChannel_YM2612::pitchBend(int16 value) { - // - Voice2612 *voice = _voices; - for (; voice; voice = voice->next) - voice->pitchBend(value); -} - -void MidiChannel_YM2612::nextTick(int *outbuf, int buflen) { - Voice2612 *voice = _voices; - for (; voice; voice = voice->next) - voice->nextTick(outbuf, buflen); -} - -void MidiChannel_YM2612::rate(uint16 r) { - _rate = r; - Voice2612 *voice = _voices; - for (; voice; voice = voice->next) - voice->_rate = r; -} - -//////////////////////////////////////// -// -// MidiDriver_YM2612 -// -//////////////////////////////////////// - -MidiDriver_YM2612::MidiDriver_YM2612(Audio::Mixer *mixer) - : MidiDriver_Emulated(mixer) { - _next_voice = 0; - - createLookupTables(); - _volume = 256; - int i; - for (i = 0; i < ARRAYSIZE(_channel); i++) - _channel[i] = new MidiChannel_YM2612; - rate(getRate()); -} - -MidiDriver_YM2612::~MidiDriver_YM2612() { - int i; - for (i = 0; i < ARRAYSIZE(_channel); i++) - delete _channel[i]; - removeLookupTables(); -} - -int MidiDriver_YM2612::open() { - if (_isOpen) - return MERR_ALREADY_OPEN; - - MidiDriver_Emulated::open(); - - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); - return 0; -} - -void MidiDriver_YM2612::close() { - if (!_isOpen) - return; - _isOpen = false; - - _mixer->stopHandle(_mixerSoundHandle); -} - -void MidiDriver_YM2612::send(uint32 b) { - send(b & 0xF, b & 0xFFFFFFF0); -} - -void MidiDriver_YM2612::send(byte chan, uint32 b) { - //byte param3 = (byte) ((b >> 24) & 0xFF); - byte param2 = (byte) ((b >> 16) & 0xFF); - byte param1 = (byte) ((b >> 8) & 0xFF); - byte cmd = (byte) (b & 0xF0); - if (chan > ARRAYSIZE(_channel)) - return; - - switch (cmd) { - case 0x80:// Note Off - _channel[chan]->noteOff(param1); - break; - case 0x90: // Note On - _channel[chan]->noteOn(param1, param2); - break; - case 0xA0: // Aftertouch - break; // Not supported. - case 0xB0: // Control Change - _channel[chan]->controlChange(param1, param2); - break; - case 0xC0: // Program Change - _channel[chan]->programChange(param1); - break; - case 0xD0: // Channel Pressure - break; // Not supported. - case 0xE0: // Pitch Bend - _channel[chan]->pitchBend((param1 | (param2 << 7)) - 0x2000); - break; - case 0xF0: // SysEx - // We should never get here! SysEx information has to be - // sent via high-level semantic methods. - warning("MidiDriver_YM2612: Receiving SysEx command on a send() call"); - break; - - default: - warning("MidiDriver_YM2612: Unknown send() command 0x%02X", cmd); - } -} - -void MidiDriver_YM2612::sysEx(const byte *msg, uint16 length) { - if (msg[0] != 0x7C || msg[1] >= ARRAYSIZE(_channel)) - return; - _channel[msg[1]]->sysEx_customInstrument('EUP ', &msg[2]); -} - -void MidiDriver_YM2612::generateSamples(int16 *data, int len) { - memset(data, 0, 2 * sizeof(int16) * len); - nextTick(data, len); -} - -void MidiDriver_YM2612::nextTick(int16 *buf1, int buflen) { - int *buf0 = (int *)buf1; - - int i; - for (i = 0; i < ARRAYSIZE(_channel); i++) - _channel[i]->nextTick(buf0, buflen); - - for (i = 0; i < buflen; ++i) - buf1[i*2+1] = buf1[i*2] = ((buf0[i] * volume()) >> 10) & 0xffff; -} - -void MidiDriver_YM2612::rate(uint16 r) -{ - int i; - for (i = 0; i < ARRAYSIZE(_channel); i++) - _channel[i]->rate(r); -} - -void MidiDriver_YM2612::createLookupTables() { - { - int i; - sintbl = new int [2048]; - for (i = 0; i < 2048; i++) - sintbl[i] = (int)(0xffff * sin(i/2048.0*2.0*PI)); - } - - { - int i; - powtbl = new int [1025]; - for (i = 0; i <= 1024; i++) - powtbl[i] = (int)(0x10000 * pow(2.0, (i-512)/512.0)); - } - - { - int i; - int block; - - static int fnum[] = { - 0x026a, 0x028f, 0x02b6, 0x02df, - 0x030b, 0x0339, 0x036a, 0x039e, - 0x03d5, 0x0410, 0x044e, 0x048f, - }; - - // (int)(880.0 * 256.0 * pow(2.0, (note-0x51)/12.0)) - // - frequencyTable = new int [120]; - for (block = -1; block < 9; block++) { - for (i = 0; i < 12; i++) { - double freq = fnum[i] * (166400.0 / 3) * pow(2.0, block-21); - frequencyTable[(block+1)*12+i] = (int)(256.0 * freq); - } - } - - keycodeTable = new int [120]; - // detune - for (block = -1; block < 9; block++) { - for (i = 0; i < 12; i++) { - // see p.204 - int f8 = (fnum[i] >> 7) & 1; - int f9 = (fnum[i] >> 8) & 1; - int f10 = (fnum[i] >> 9) & 1; - int f11 = (fnum[i] >> 10) & 1; - int n4 = f11; - int n3 = (f11&(f10|f9|f8)) | (~f11&f10&f9&f8); - int note = n4*2 + n3; - // see p.207 - keycodeTable[(block+1)*12+i] = block*4 + note; - } - } - } - - { - int freq; - keyscaleTable = new int [8192]; - keyscaleTable[0] = 0; - for (freq = 1; freq < 8192; freq++) { - keyscaleTable[freq] = (int)(log((double)freq) / 9.03 * 32.0) - 1; - // 8368[Hz] (o9c) - } - } - - { - int i; - attackOut = new int [1024]; - for (i = 0; i < 1024; i++) - attackOut[i] = (int)(((0x7fff+0x03a5)*30.0) / (30.0+i)) - 0x03a5; - } -} - -void MidiDriver_YM2612::removeLookupTables() { - delete[] sintbl; - delete[] powtbl; - delete[] frequencyTable; - delete[] keycodeTable; - delete[] keyscaleTable; - delete[] attackOut; - sintbl = powtbl = frequencyTable = keycodeTable = keyscaleTable = attackOut = 0; -} - - -// Plugin interface - -class TownsEmuMusicPlugin : public MusicPluginObject { -public: - const char *getName() const { - return _s("FM Towns Emulator"); - } - - const char *getId() const { - return "towns"; - } - - MusicDevices getDevices() const; - Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const; -}; - -MusicDevices TownsEmuMusicPlugin::getDevices() const { - MusicDevices devices; - devices.push_back(MusicDevice(this, "", MT_TOWNS)); - return devices; -} - -Common::Error TownsEmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const { - *mididriver = new MidiDriver_YM2612(g_system->getMixer()); - - return Common::kNoError; -} - -//#if PLUGIN_ENABLED_DYNAMIC(TOWNS) - //REGISTER_PLUGIN_DYNAMIC(TOWNS, PLUGIN_TYPE_MUSIC, TownsEmuMusicPlugin); -//#else - REGISTER_PLUGIN_STATIC(TOWNS, PLUGIN_TYPE_MUSIC, TownsEmuMusicPlugin); -//#endif diff --git a/sound/softsynth/ym2612.h b/sound/softsynth/ym2612.h deleted file mode 100644 index 5fb2f32ef0..0000000000 --- a/sound/softsynth/ym2612.h +++ /dev/null @@ -1,179 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#ifndef SOUND_SOFTSYNTH_Y2612_H -#define SOUND_SOFTSYNTH_Y2612_H - -#include "common/scummsys.h" - -#include "sound/softsynth/emumidi.h" - -//////////////////////////////////////// -// -// Class declarations -// -//////////////////////////////////////// - -class Voice2612; -class Operator2612 { -protected: - Voice2612 *_owner; - enum State { _s_ready, _s_attacking, _s_decaying, _s_sustaining, _s_releasing }; - State _state; - int32 _currentLevel; - int _frequency; - uint32 _phase; - int _lastOutput; - int _feedbackLevel; - int _detune; - int _multiple; - int32 _totalLevel; - int _keyScale; - int _velocity; - int _specifiedTotalLevel; - int _specifiedAttackRate; - int _specifiedDecayRate; - int _specifiedSustainLevel; - int _specifiedSustainRate; - int _specifiedReleaseRate; - int _tickCount; - int _attackTime; - int32 _decayRate; - int32 _sustainLevel; - int32 _sustainRate; - int32 _releaseRate; - -public: - Operator2612 (Voice2612 *owner); - ~Operator2612(); - void feedbackLevel(int level); - void setInstrument(byte const *instrument); - void velocity(int velo); - void keyOn(); - void keyOff(); - void frequency(int freq); - void nextTick(const int *phaseShift, int *outbuf, int buflen); - bool inUse() { return (_state != _s_ready); } -}; - -class Voice2612 { -public: - Voice2612 *next; - uint16 _rate; - -protected: - Operator2612 *_opr[4]; - int _velocity; - int _control7; - int _note; - int _frequencyOffs; - int _frequency; - int _algorithm; - - int *_buffer; - int _buflen; - -public: - Voice2612(); - ~Voice2612(); - void setControlParameter(int control, int value); - void setInstrument(byte const *instrument); - void velocity(int velo); - void nextTick(int *outbuf, int buflen); - void noteOn(int n, int onVelo); - bool noteOff(int note); - void pitchBend(int value); - void recalculateFrequency(); -}; - -class MidiChannel_YM2612 : public MidiChannel { -protected: - uint16 _rate; - Voice2612 *_voices; - Voice2612 *_next_voice; - -public: - void removeAllVoices(); - void nextTick(int *outbuf, int buflen); - void rate(uint16 r); - -public: - MidiChannel_YM2612(); - virtual ~MidiChannel_YM2612(); - - // MidiChannel interface - MidiDriver *device() { return 0; } - byte getNumber() { return 0; } - void release() { } - void send(uint32 b) { } - void noteOff(byte note); - void noteOn(byte note, byte onVelo); - void programChange(byte program) { } - void pitchBend(int16 value); - void controlChange(byte control, byte value); - void pitchBendFactor(byte value) { } - void sysEx_customInstrument(uint32 type, const byte *instr); -}; - -class MidiDriver_YM2612 : public MidiDriver_Emulated { -protected: - MidiChannel_YM2612 *_channel[16]; - - int _next_voice; - int _volume; - -protected: - void nextTick(int16 *buf1, int buflen); - int volume(int val = -1) { if (val >= 0) _volume = val; return _volume; } - void rate(uint16 r); - - void generateSamples(int16 *buf, int len); - -public: - MidiDriver_YM2612(Audio::Mixer *mixer); - virtual ~MidiDriver_YM2612(); - - static void createLookupTables(); - static void removeLookupTables(); - - int open(); - void close(); - void send(uint32 b); - void send(byte channel, uint32 b); // Supports higher than channel 15 - uint32 property(int prop, uint32 param) { return 0; } - - void setPitchBendRange(byte channel, uint range) { } - void sysEx(const byte *msg, uint16 length); - - MidiChannel *allocateChannel() { return 0; } - MidiChannel *getPercussionChannel() { return 0; } - - - // AudioStream API - bool isStereo() const { return true; } - int getRate() const { return _mixer->getOutputRate(); } -}; - -#endif - diff --git a/sound/timestamp.cpp b/sound/timestamp.cpp deleted file mode 100644 index 1e49e1b476..0000000000 --- a/sound/timestamp.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "sound/timestamp.h" -#include "common/algorithm.h" - -namespace Audio { - -Timestamp::Timestamp(uint ms, uint fr) { - assert(fr > 0); - - _secs = ms / 1000; - _framerateFactor = 1000 / Common::gcd(1000, fr); - _framerate = fr * _framerateFactor; - - // Note that _framerate is always divisible by 1000. - _numFrames = (ms % 1000) * (_framerate / 1000); -} - -Timestamp::Timestamp(uint s, uint frames, uint fr) { - assert(fr > 0); - - _secs = s; - _framerateFactor = 1000 / Common::gcd(1000, fr); - _framerate = fr * _framerateFactor; - _numFrames = frames * _framerateFactor; - - normalize(); -} - -Timestamp Timestamp::convertToFramerate(uint newFramerate) const { - Timestamp ts(*this); - - if (ts.framerate() != newFramerate) { - ts._framerateFactor = 1000 / Common::gcd(1000, newFramerate); - ts._framerate = newFramerate * ts._framerateFactor; - - const uint g = Common::gcd(_framerate, ts._framerate); - const uint p = _framerate / g; - const uint q = ts._framerate / g; - - // Convert the frame offset to the new framerate. - // We round to the nearest (as opposed to always - // rounding down), to minimize rounding errors during - // round trip conversions. - ts._numFrames = (ts._numFrames * q + p/2) / p; - - ts.normalize(); - } - - return ts; -} - -void Timestamp::normalize() { - // Convert negative _numFrames values to positive ones by adjusting _secs - if (_numFrames < 0) { - int secsub = 1 + (-_numFrames / _framerate); - - _numFrames += _framerate * secsub; - _secs -= secsub; - } - - // Wrap around if necessary - _secs += (_numFrames / _framerate); - _numFrames %= _framerate; -} - -bool Timestamp::operator==(const Timestamp &ts) const { - return cmp(ts) == 0; -} - -bool Timestamp::operator!=(const Timestamp &ts) const { - return cmp(ts) != 0; -} - -bool Timestamp::operator<(const Timestamp &ts) const { - return cmp(ts) < 0; -} - -bool Timestamp::operator<=(const Timestamp &ts) const { - return cmp(ts) <= 0; -} - -bool Timestamp::operator>(const Timestamp &ts) const { - return cmp(ts) > 0; -} - -bool Timestamp::operator>=(const Timestamp &ts) const { - return cmp(ts) >= 0; -} - -int Timestamp::cmp(const Timestamp &ts) const { - int delta = _secs - ts._secs; - if (!delta) { - const uint g = Common::gcd(_framerate, ts._framerate); - const uint p = _framerate / g; - const uint q = ts._framerate / g; - - delta = (_numFrames * q - ts._numFrames * p); - } - - return delta; -} - - -Timestamp Timestamp::addFrames(int frames) const { - Timestamp ts(*this); - - // The frames are given in the original framerate, so we have to - // adjust by _framerateFactor accordingly. - ts._numFrames += frames * _framerateFactor; - ts.normalize(); - - return ts; -} - -Timestamp Timestamp::addMsecs(int ms) const { - Timestamp ts(*this); - ts._secs += ms / 1000; - // Add the remaining frames. Note that _framerate is always divisible by 1000. - ts._numFrames += (ms % 1000) * (ts._framerate / 1000); - - ts.normalize(); - - return ts; -} - -void Timestamp::addIntern(const Timestamp &ts) { - assert(_framerate == ts._framerate); - _secs += ts._secs; - _numFrames += ts._numFrames; - - normalize(); -} - -Timestamp Timestamp::operator-() const { - Timestamp result(*this); - result._secs = -_secs; - result._numFrames = -_numFrames; - result.normalize(); - return result; -} - -Timestamp Timestamp::operator+(const Timestamp &ts) const { - Timestamp result(*this); - result.addIntern(ts); - return result; -} - -Timestamp Timestamp::operator-(const Timestamp &ts) const { - Timestamp result(*this); - result.addIntern(-ts); - return result; -} - -int Timestamp::frameDiff(const Timestamp &ts) const { - - int delta = 0; - if (_secs != ts._secs) - delta = (_secs - ts._secs) * _framerate; - - delta += _numFrames; - - if (_framerate == ts._framerate) { - delta -= ts._numFrames; - } else { - // We need to multiply by the quotient of the two framerates. - // We cancel the GCD in this fraction to reduce the risk of - // overflows. - const uint g = Common::gcd(_framerate, ts._framerate); - const uint p = _framerate / g; - const uint q = ts._framerate / g; - - delta -= ((long)ts._numFrames * p + q/2) / (long)q; - } - - return delta / (int)_framerateFactor; -} - -int Timestamp::msecsDiff(const Timestamp &ts) const { - return msecs() - ts.msecs(); -} - -int Timestamp::msecs() const { - // Note that _framerate is always divisible by 1000. - return _secs * 1000 + _numFrames / (_framerate / 1000); -} - - -} // End of namespace Audio diff --git a/sound/timestamp.h b/sound/timestamp.h deleted file mode 100644 index 4130793fc8..0000000000 --- a/sound/timestamp.h +++ /dev/null @@ -1,251 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef SOUND_TIMESTAMP_H -#define SOUND_TIMESTAMP_H - -#include "common/scummsys.h" - -namespace Audio { - -/** - * Timestamps allow specifying points in time and measuring time intervals - * with a sub-millisecond granularity. - * - * When dealing with audio and video decoding, it is often necessary to - * measure time (intervals) in terms of frames, relative to a fixed - * frame rate (that is, a fixed number of frames per seconds). For - * example, in a typical video there are 24 frames per second, and in a - * typical sound there are 44100 frames (i.e. samples for mono sound - * and pairs of samples for stereo) per second. - * - * At the same time, the system clock provided by ScummVM measures time - * in milliseconds. For syncing purposes and other reasons, it is often - * necessary to convert between and compare time measures given on the - * one hand as a frame count, and on the other hand as a number of - * milliseconds. - * - * If handled carelessly, this can introduce rounding errors that - * quickly accumulate, resulting in user noticeable disturbance, such as - * audio and video running out of sync. E.g. a typical approach is to - * measure all time in milliseconds. But with a frame rate of 24 frames - * per second, one frame is 41.66666... milliseconds long. On the other - * hand, if measuring in frames, then similar rounding issue occur when - * converting from milliseconds to frames. - * - * One solution is to use floating point arithmetic to compute with - * fractional frames resp. (milli)seconds. This has other undesirable - * side effects; foremost, some platforms ScummVM runs on still have - * only limited (and slow) floating point support. - * - * This class provides an alternate solution: It stores time in terms of - * frames, but with a twist: Client code can specify arbitrary - * (integral) framerates; but internally, Timestamp modifies the - * framerate to be a multiple of 1000. This way, both numbers of frames - * (relative to the original framerate) as well as milliseconds can be - * represented as integers. This change is completely hidden from the - * user, however. - * - * A Timestamp can be converted to a frame count or milliseconds at - * virtually no cost. Likewise, it is posible to compute the difference - * between two Timestamps in milliseconds or number of frames. - * Timestamps can be easily compared using regular comparison operators, - * resulting in nicely readable code; this is even possible for - * timestamps that are specified using different framerates. - * Client code can modify Timestamps by adding a number of frames - * to it, or adding a number of milliseconds. Adding negative amounts is - * also allowed, and a Timestamp can even represent a "negative time" - * (mainly useful when using the Timestamp to store a time interval). - */ -class Timestamp { -public: - /** - * Set up a timestamp with a given time and framerate. - * @param msecs starting time in milliseconds - * @param framerate number of frames per second (must be > 0) - */ - Timestamp(uint msecs = 0, uint framerate = 1); - - /** - * Set up a timestamp with a given time, frames and framerate. - * @param secs starting time in seconds - * @param frames starting frames - * @param framerate number of frames per second (must be > 0) - */ - Timestamp(uint secs, uint frames, uint framerate); - - /** - * Return a timestamp which represents as closely as possible - * the point in time describes by this timestamp, but with - * a different framerate. - */ - Timestamp convertToFramerate(uint newFramerate) const; - - /** - * Check whether to timestamps describe the exact same moment - * in time. This means that two timestamps can compare - * as equal even if they use different framerates. - */ - bool operator==(const Timestamp &ts) const; - bool operator!=(const Timestamp &ts) const; - bool operator<(const Timestamp &ts) const; - bool operator<=(const Timestamp &ts) const; - bool operator>(const Timestamp &ts) const; - bool operator>=(const Timestamp &ts) const; - - /** - * Returns a new timestamp, which corresponds to the time encoded - * by this timestamp with the given number of frames added. - * @param frames number of frames to add - */ - Timestamp addFrames(int frames) const; - - /** - * Returns a new timestamp, which corresponds to the time encoded - * by this timestamp with the given number of milliseconds added. - * @param msecs number of milliseconds to add - */ - Timestamp addMsecs(int msecs) const; - - - // unary minus - Timestamp operator-() const; - - /** - * Compute the sum of two timestamps. This is only - * allowed if they use the same framerate. - */ - Timestamp operator+(const Timestamp &ts) const; - - /** - * Compute the difference between two timestamps. This is only - * allowed if they use the same framerate. - */ - Timestamp operator-(const Timestamp &ts) const; - - /** - * Computes the number of frames between this timestamp and ts. - * The frames are with respect to the framerate used by this - * Timestamp (which may differ from the framerate used by ts). - */ - int frameDiff(const Timestamp &ts) const; - - /** Computes the number off milliseconds between this timestamp and ts. */ - int msecsDiff(const Timestamp &ts) const; - - /** - * Return the time in milliseconds described by this timestamp, - * rounded down. - */ - int msecs() const; - - /** - * Return the time in seconds described by this timestamp, - * rounded down. - */ - inline int secs() const { - return _secs; - } - - /** - * Return the time in frames described by this timestamp. - */ - inline int totalNumberOfFrames() const { - return _numFrames / (int)_framerateFactor + _secs * (int)(_framerate / _framerateFactor); - } - - /** - * A timestamp consists of a number of seconds, plus a number - * of frames, the latter describing a fraction of a second. - * This method returns the latter number. - */ - inline int numberOfFrames() const { - return _numFrames / (int)_framerateFactor; - } - - /** Return the framerate used by this timestamp. */ - inline uint framerate() const { return _framerate / _framerateFactor; } - -protected: - /** - * Compare this timestamp to another one and return - * a value similar to strcmp. - */ - int cmp(const Timestamp &ts) const; - - /** - * Normalize this timestamp by making _numFrames non-negative - * and reducing it modulo _framerate. - */ - void normalize(); - - /** - * Add another timestamp to this one and normalize the result. - */ - void addIntern(const Timestamp &ts); - -protected: - /** - * The seconds part of this timestamp. - * The total time in seconds represented by this timestamp can be - * computed as follows: - * _secs + (double)_numFrames / _framerate - */ - int _secs; - - /** - * The number of frames which together with _secs encodes the - * timestamp. The total number of *internal* frames represented - * by this timestamp can be computed as follows: - * _numFrames + _secs * _framerate - * To obtain the number of frames with respect to the original - * framerate, this value has to be divided by _framerateFactor. - * - * This is always a value greater or equal to zero. - * The only reason this is an int and not an uint is to - * allow intermediate negative values. - */ - int _numFrames; - - /** - * The internal framerate, i.e. the number of frames per second. - * This is computed as the least common multiple of the framerate - * specified by the client code, and 1000. - * This way, we ensure that we can store both frames and - * milliseconds without any rounding losses. - */ - uint _framerate; - - /** - * Factor by which the original framerate specified by the client - * code was multipled to obtain the internal _framerate value. - */ - uint _framerateFactor; -}; - - -} // End of namespace Audio - -#endif diff --git a/test/audio/audiostream.h b/test/audio/audiostream.h new file mode 100644 index 0000000000..1ffb2308ec --- /dev/null +++ b/test/audio/audiostream.h @@ -0,0 +1,209 @@ +#include + +#include "audio/audiostream.h" + +#include "helper.h" + +class AudioStreamTestSuite : public CxxTest::TestSuite +{ +public: + void test_convertTimeToStreamPos() { + const Audio::Timestamp a = Audio::convertTimeToStreamPos(Audio::Timestamp(500, 1000), 11025, true); + // The last bit has to be 0 in any case for a stereo stream. + TS_ASSERT_EQUALS(a.totalNumberOfFrames() & 1, 0); + + // TODO: This test is rather hacky... actually converTimeToStreamPos might also return 11026 + // instead of 11024 and it would still be a valid sample position for ~500ms. + TS_ASSERT_EQUALS(a.totalNumberOfFrames(), 11024); + + const Audio::Timestamp b = Audio::convertTimeToStreamPos(Audio::Timestamp(500, 1000), 11025, false); + TS_ASSERT_EQUALS(b.totalNumberOfFrames(), 500 * 11025 / 1000); + + // Test Audio CD positioning + + // for 44kHz and stereo + const Audio::Timestamp c = Audio::convertTimeToStreamPos(Audio::Timestamp(0, 50, 75), 44100, true); + TS_ASSERT_EQUALS(c.totalNumberOfFrames(), 50 * 44100 * 2 / 75); + + // for 11kHz and mono + const Audio::Timestamp d = Audio::convertTimeToStreamPos(Audio::Timestamp(0, 50, 75), 11025, false); + TS_ASSERT_EQUALS(d.totalNumberOfFrames(), 50 * 11025 / 75); + + // Some misc test + const Audio::Timestamp e = Audio::convertTimeToStreamPos(Audio::Timestamp(1, 1, 4), 11025, false); + TS_ASSERT_EQUALS(e.totalNumberOfFrames(), 5 * 11025 / 4); + } + +private: + void testLoopingAudioStreamFixedIter(const int sampleRate, const bool isStereo) { + const int secondLength = sampleRate * (isStereo ? 2 : 1); + + int16 *sine = 0; + Audio::SeekableAudioStream *s = createSineStream(sampleRate, 1, &sine, false, isStereo); + Audio::LoopingAudioStream *loop = new Audio::LoopingAudioStream(s, 7); + + int16 *buffer = new int16[secondLength * 3]; + + // Check parameters + TS_ASSERT_EQUALS(loop->isStereo(), isStereo); + TS_ASSERT_EQUALS(loop->getRate(), sampleRate); + TS_ASSERT_EQUALS(loop->endOfData(), false); + TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)0); + + // Read one second + TS_ASSERT_EQUALS(loop->readBuffer(buffer, secondLength), secondLength); + TS_ASSERT_EQUALS(memcmp(buffer, sine, secondLength * sizeof(int16)), 0); + + TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)1); + TS_ASSERT_EQUALS(loop->endOfData(), false); + + // Read two seconds + TS_ASSERT_EQUALS(loop->readBuffer(buffer, secondLength * 2), secondLength * 2); + TS_ASSERT_EQUALS(memcmp(buffer, sine, secondLength * sizeof(int16)), 0); + TS_ASSERT_EQUALS(memcmp(buffer + secondLength, sine, secondLength * sizeof(int16)), 0); + + TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)3); + TS_ASSERT_EQUALS(loop->endOfData(), false); + + // Read three seconds + TS_ASSERT_EQUALS(loop->readBuffer(buffer, secondLength * 3), secondLength * 3); + TS_ASSERT_EQUALS(memcmp(buffer, sine, secondLength * sizeof(int16)), 0); + TS_ASSERT_EQUALS(memcmp(buffer + secondLength, sine, secondLength * sizeof(int16)), 0); + TS_ASSERT_EQUALS(memcmp(buffer + secondLength * 2, sine, secondLength * sizeof(int16)), 0); + + TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)6); + TS_ASSERT_EQUALS(loop->endOfData(), false); + + // Read the last second in two parts + const int firstStep = secondLength / 2; + const int secondStep = secondLength - firstStep; + + TS_ASSERT_EQUALS(loop->readBuffer(buffer, firstStep), firstStep); + TS_ASSERT_EQUALS(memcmp(buffer, sine, firstStep * sizeof(int16)), 0); + + TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)6); + TS_ASSERT_EQUALS(loop->endOfData(), false); + + TS_ASSERT_EQUALS(loop->readBuffer(buffer, secondLength), secondStep); + TS_ASSERT_EQUALS(memcmp(buffer, sine + firstStep, secondStep * sizeof(int16)), 0); + + TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)7); + TS_ASSERT_EQUALS(loop->endOfData(), true); + + // Try to read beyond the end of the stream + TS_ASSERT_EQUALS(loop->readBuffer(buffer, secondLength), 0); + TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)7); + TS_ASSERT_EQUALS(loop->endOfData(), true); + + delete[] buffer; + delete loop; + delete[] sine; + } + +public: + void test_looping_audio_stream_mono_11025_fixed_iter() { + testLoopingAudioStreamFixedIter(11025, false); + } + + void test_looping_audio_stream_mono_22050_fixed_iter() { + testLoopingAudioStreamFixedIter(22050, false); + } + + void test_looping_audio_stream_stereo_11025_fixed_iter() { + testLoopingAudioStreamFixedIter(11025, true); + } + + void test_looping_audio_stream_stereo_22050_fixed_iter() { + testLoopingAudioStreamFixedIter(22050, true); + } + +private: + void testSubLoopingAudioStreamFixedIter(const int sampleRate, const bool isStereo, const int time, const int loopEndTime) { + const int secondLength = sampleRate * (isStereo ? 2 : 1); + + const Audio::Timestamp loopStart(500, 1000), loopEnd(loopEndTime * 1000, 1000); + + const int32 loopStartPos = Audio::convertTimeToStreamPos(loopStart, sampleRate, isStereo).totalNumberOfFrames(); + const int32 loopEndPos = Audio::convertTimeToStreamPos(loopEnd, sampleRate, isStereo).totalNumberOfFrames(); + + const int32 loopIteration = loopEndPos - loopStartPos; + + int16 *sine = 0; + Audio::SeekableAudioStream *s = createSineStream(sampleRate, time, &sine, false, isStereo); + Audio::SubLoopingAudioStream *loop = new Audio::SubLoopingAudioStream(s, 5, loopStart, loopEnd); + + const int32 bufferLen = MAX(loopIteration * 3, loopEndPos); + int16 *buffer = new int16[bufferLen]; + + // Check parameters + TS_ASSERT_EQUALS(loop->isStereo(), isStereo); + TS_ASSERT_EQUALS(loop->getRate(), sampleRate); + TS_ASSERT_EQUALS(loop->endOfData(), false); + + // Read the non-looped part + one iteration + TS_ASSERT_EQUALS(loop->readBuffer(buffer, loopEndPos), loopEndPos); + TS_ASSERT_EQUALS(memcmp(buffer, sine, loopEndPos * sizeof(int16)), 0); + TS_ASSERT_EQUALS(loop->endOfData(), false); + + // We should have one full iteration now + + // Read another loop iteration + TS_ASSERT_EQUALS(loop->readBuffer(buffer, loopIteration), loopIteration); + TS_ASSERT_EQUALS(memcmp(buffer, sine + loopStartPos, loopIteration * sizeof(int16)), 0); + TS_ASSERT_EQUALS(loop->endOfData(), false); + + // We should have two full iterations now + + // Read three loop iterations at once + TS_ASSERT_EQUALS(loop->readBuffer(buffer, loopIteration * 3), loopIteration * 3); + TS_ASSERT_EQUALS(memcmp(buffer + loopIteration * 0, sine + loopStartPos, loopIteration * sizeof(int16)), 0); + TS_ASSERT_EQUALS(memcmp(buffer + loopIteration * 1, sine + loopStartPos, loopIteration * sizeof(int16)), 0); + TS_ASSERT_EQUALS(memcmp(buffer + loopIteration * 2, sine + loopStartPos, loopIteration * sizeof(int16)), 0); + TS_ASSERT_EQUALS(loop->endOfData(), true); + + // We should have five full iterations now, thus the stream should be done + + // Try to read beyond the end of the stream (note this only applies, till we define that SubLoopingAudioStream should + // stop playing after the looped area). + TS_ASSERT_EQUALS(loop->readBuffer(buffer, secondLength), 0); + TS_ASSERT_EQUALS(loop->endOfData(), true); + + delete[] buffer; + delete loop; + delete[] sine; + } + +public: + void test_sub_looping_audio_stream_mono_11025_mid_fixed_iter() { + testSubLoopingAudioStreamFixedIter(11025, false, 2, 1); + } + + void test_sub_looping_audio_stream_mono_22050_mid_fixed_iter() { + testSubLoopingAudioStreamFixedIter(22050, false, 2, 1); + } + + void test_sub_looping_audio_stream_stereo_11025_mid_fixed_iter() { + testSubLoopingAudioStreamFixedIter(11025, true, 2, 1); + } + + void test_sub_looping_audio_stream_stereo_22050_mid_fixed_iter() { + testSubLoopingAudioStreamFixedIter(22050, true, 2, 1); + } + + void test_sub_looping_audio_stream_mono_11025_end_fixed_iter() { + testSubLoopingAudioStreamFixedIter(11025, false, 2, 2); + } + + void test_sub_looping_audio_stream_mono_22050_end_fixed_iter() { + testSubLoopingAudioStreamFixedIter(22050, false, 2, 2); + } + + void test_sub_looping_audio_stream_stereo_11025_end_fixed_iter() { + testSubLoopingAudioStreamFixedIter(11025, true, 2, 2); + } + + void test_sub_looping_audio_stream_stereo_22050_end_fixed_iter() { + testSubLoopingAudioStreamFixedIter(22050, true, 2, 2); + } +}; + diff --git a/test/audio/helper.h b/test/audio/helper.h new file mode 100644 index 0000000000..262ca1c060 --- /dev/null +++ b/test/audio/helper.h @@ -0,0 +1,112 @@ +#ifndef TEST_SOUND_HELPER_H +#define TEST_SOUND_HELPER_H + +#include "audio/decoders/raw.h" + +#include "common/stream.h" +#include "common/endian.h" + +#include +#include + +template +static T *createSine(const int sampleRate, const int time) { + T *sine = (T *)malloc(sizeof(T) * time * sampleRate); + + const bool isUnsigned = !std::numeric_limits::is_signed; + const T xorMask = isUnsigned ? (1 << (std::numeric_limits::digits - 1)) : 0; + const T maxValue = std::numeric_limits::max() ^ xorMask; + + for (int i = 0; i < time * sampleRate; ++i) + sine[i] = ((T)(sin((double)i / sampleRate * 2 * PI) * maxValue)) ^ xorMask; + + return sine; +} + +template +static Common::SeekableReadStream *createPartitionStream(T *sine, const int samples, Audio::RawStreamBlockList &blockList) { + const int block1Len = samples / 2; + const int block1Size = block1Len * sizeof(T); + const int block2Len = samples - block1Len; + const int block2Size = block2Len * sizeof(T); + + const int bufferLen = samples * 2; + const int bufferSize = bufferLen * sizeof(T); + T *partition = (T *)calloc(1, bufferSize); + + Audio::RawStreamBlock block; + + // The will layout the buffer like the following: + // [Zero], [Part2], [Zero], [Part1] + + // The first part of the stream is at the end of the memory buffer + block.pos = bufferSize - block1Size; + block.len = block1Len; + memcpy(partition + bufferLen - block1Len, sine, block1Size); + blockList.push_back(block); + + // The second part of the stream is near the beginning of the memory buffer + block.pos = block2Size; + block.len = block2Len; + memcpy(partition + block2Len, sine + block1Len, block2Size); + blockList.push_back(block); + + free(sine); + + return new Common::MemoryReadStream((const byte *)partition, bufferSize, DisposeAfterUse::YES); +} + +template +static Audio::SeekableAudioStream *createSineStream(const int sampleRate, const int time, int16 **comp, bool le, bool isStereo, bool makePartition = false) { + T *sine = createSine(sampleRate, time * (isStereo ? 2 : 1)); + + const bool isUnsigned = !std::numeric_limits::is_signed; + const T xorMask = isUnsigned ? (1 << (std::numeric_limits::digits - 1)) : 0; + const bool is16Bits = (sizeof(T) == 2); + assert(sizeof(T) == 2 || sizeof(T) == 1); + + const int samples = sampleRate * time * (isStereo ? 2 : 1); + + if (comp) { + *comp = new int16[samples]; + for (int i = 0; i < samples; ++i) { + if (is16Bits) + (*comp)[i] = sine[i] ^ xorMask; + else + (*comp)[i] = (sine[i] ^ xorMask) << 8; + } + } + + if (is16Bits) { + if (le) { + for (int i = 0; i < samples; ++i) + WRITE_LE_UINT16(&sine[i], sine[i]); + } else { + for (int i = 0; i < samples; ++i) + WRITE_BE_UINT16(&sine[i], sine[i]); + } + } + + Audio::SeekableAudioStream *s = 0; + if (makePartition) { + Audio::RawStreamBlockList blockList; + Common::SeekableReadStream *sD = createPartitionStream(sine, samples, blockList); + s = Audio::makeRawStream(sD, blockList, sampleRate, + (is16Bits ? Audio::FLAG_16BITS : 0) + | (isUnsigned ? Audio::FLAG_UNSIGNED : 0) + | (le ? Audio::FLAG_LITTLE_ENDIAN : 0) + | (isStereo ? Audio::FLAG_STEREO : 0)); + } else { + Common::SeekableReadStream *sD = new Common::MemoryReadStream((const byte *)sine, sizeof(T) * samples, DisposeAfterUse::YES); + s = Audio::makeRawStream(sD, sampleRate, + (is16Bits ? Audio::FLAG_16BITS : 0) + | (isUnsigned ? Audio::FLAG_UNSIGNED : 0) + | (le ? Audio::FLAG_LITTLE_ENDIAN : 0) + | (isStereo ? Audio::FLAG_STEREO : 0)); + } + + return s; +} + +#endif + diff --git a/test/audio/raw.h b/test/audio/raw.h new file mode 100644 index 0000000000..51ec067f7e --- /dev/null +++ b/test/audio/raw.h @@ -0,0 +1,358 @@ +#include + +#include "audio/decoders/raw.h" + +#include "helper.h" + +class RawStreamTestSuite : public CxxTest::TestSuite +{ +private: + template + void readBufferTestTemplate(const int sampleRate, const int time, const bool le, const bool isStereo, const bool makePartition = false) { + int16 *sine; + Audio::SeekableAudioStream *s = createSineStream(sampleRate, time, &sine, le, isStereo, makePartition); + + const int totalSamples = sampleRate * time * (isStereo ? 2 : 1); + int16 *buffer = new int16[totalSamples]; + TS_ASSERT_EQUALS(s->readBuffer(buffer, totalSamples), totalSamples); + TS_ASSERT_EQUALS(memcmp(sine, buffer, sizeof(int16) * totalSamples), 0); + TS_ASSERT_EQUALS(s->endOfData(), true); + + delete[] sine; + delete[] buffer; + delete s; + } + +public: + void test_read_buffer_8_bit_signed_mono() { + readBufferTestTemplate(11025, 2, false, false); + } + + void test_read_buffer_8_bit_signed_stereo() { + readBufferTestTemplate(11025, 2, false, true); + } + + void test_read_buffer_8_bit_unsigned_mono() { + readBufferTestTemplate(11025, 2, false, false); + } + + void test_read_buffer_16_bit_signed_be_mono() { + readBufferTestTemplate(11025, 2, false, false); + } + + void test_read_buffer_16_bit_signed_be_stereo() { + readBufferTestTemplate(11025, 2, false, true); + } + + void test_read_buffer_16_bit_unsigned_be_mono() { + readBufferTestTemplate(11025, 2, false, false); + } + + void test_read_buffer_16_bit_unsigned_be_stereo() { + readBufferTestTemplate(11025, 2, false, true); + } + + void test_read_buffer_16_bit_signed_le_mono() { + readBufferTestTemplate(11025, 2, true, false); + } + + void test_read_buffer_16_bit_signed_le_stereo() { + readBufferTestTemplate(11025, 2, true, true); + } + + void test_read_buffer_16_bit_unsigned_le_mono() { + readBufferTestTemplate(11025, 2, true, false); + } + + void test_read_buffer_16_bit_unsigned_le_stereo() { + readBufferTestTemplate(11025, 2, true, true); + } + + void test_read_buffer_8_bit_signed_mono_parted() { + readBufferTestTemplate(11025, 2, false, false, true); + } + + void test_read_buffer_8_bit_signed_stereo_parted() { + readBufferTestTemplate(11025, 2, false, true, true); + } + + void test_read_buffer_8_bit_unsigned_mono_parted() { + readBufferTestTemplate(11025, 2, false, false, true); + } + + void test_read_buffer_16_bit_signed_be_mono_parted() { + readBufferTestTemplate(11025, 2, false, false, true); + } + + void test_read_buffer_16_bit_signed_be_stereo_parted() { + readBufferTestTemplate(11025, 2, false, true, true); + } + + void test_read_buffer_16_bit_unsigned_be_mono_parted() { + readBufferTestTemplate(11025, 2, false, false, true); + } + + void test_read_buffer_16_bit_unsigned_be_stereo_parted() { + readBufferTestTemplate(11025, 2, false, true, true); + } + + void test_read_buffer_16_bit_signed_le_mono_parted() { + readBufferTestTemplate(11025, 2, true, false, true); + } + + void test_read_buffer_16_bit_signed_le_stereo_parted() { + readBufferTestTemplate(11025, 2, true, true, true); + } + + void test_read_buffer_16_bit_unsigned_le_mono_parted() { + readBufferTestTemplate(11025, 2, true, false, true); + } + + void test_read_buffer_16_bit_unsigned_le_stereo_parted() { + readBufferTestTemplate(11025, 2, true, true, true); + } + +private: + void partialReadTest(const bool makePartition) { + const int sampleRate = 11025; + const int time = 4; + + int16 *sine; + Audio::SeekableAudioStream *s = createSineStream(sampleRate, time, &sine, false, false, makePartition); + int16 *buffer = new int16[sampleRate * time]; + + TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate), sampleRate); + TS_ASSERT_EQUALS(memcmp(sine, buffer, sampleRate), 0); + TS_ASSERT_EQUALS(s->endOfData(), false); + + TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate * 2), sampleRate * 2); + TS_ASSERT_EQUALS(memcmp(sine + sampleRate, buffer, sampleRate * 2), 0); + TS_ASSERT_EQUALS(s->endOfData(), false); + + TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate), sampleRate); + TS_ASSERT_EQUALS(memcmp(sine + sampleRate * 3, buffer, sampleRate), 0); + TS_ASSERT_EQUALS(s->endOfData(), true); + + delete[] sine; + delete[] buffer; + delete s; + } +public: + void test_partial_read() { + partialReadTest(false); + } + + void test_partial_read_parted() { + partialReadTest(true); + } + +private: + void readAfterEndTest(const bool makePartition) { + const int sampleRate = 11025; + const int time = 1; + Audio::SeekableAudioStream *s = createSineStream(sampleRate, time, 0, false, false); + int16 *buffer = new int16[sampleRate * time]; + + TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate * time), sampleRate * time); + TS_ASSERT_EQUALS(s->endOfData(), true); + + TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate * time), 0); + TS_ASSERT_EQUALS(s->endOfData(), true); + + delete[] buffer; + delete s; + } + +public: + void test_read_after_end() { + readAfterEndTest(false); + } + + void test_read_after_end_parted() { + readAfterEndTest(true); + } + +private: + void rewindTest(const bool makePartition) { + const int sampleRate = 11025; + const int time = 2; + Audio::SeekableAudioStream *s = createSineStream(sampleRate, time, 0, false, false, makePartition); + int16 *buffer = new int16[sampleRate * time]; + + TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate * time), sampleRate * time); + TS_ASSERT_EQUALS(s->endOfData(), true); + + s->rewind(); + TS_ASSERT_EQUALS(s->endOfData(), false); + + TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate * time), sampleRate * time); + TS_ASSERT_EQUALS(s->endOfData(), true); + + delete[] buffer; + delete s; + } +public: + void test_rewind() { + rewindTest(false); + } + + void test_rewind_parted() { + rewindTest(true); + } + +private: + void lengthTest(const bool makePartition) { + int sampleRate = 0; + const int time = 4; + + Audio::SeekableAudioStream *s = 0; + + // 11025 Hz tests + sampleRate = 11025; + s = createSineStream(sampleRate, time, 0, false, false, makePartition); + TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); + delete s; + + s = createSineStream(sampleRate, time, 0, false, false, makePartition); + TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); + delete s; + + // 48000 Hz tests + sampleRate = 48000; + s = createSineStream(sampleRate, time, 0, false, false, makePartition); + TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); + delete s; + + s = createSineStream(sampleRate, time, 0, true, false, makePartition); + TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); + delete s; + + // 11840 Hz tests + sampleRate = 11840; + s = createSineStream(sampleRate, time, 0, false, false, makePartition); + TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); + delete s; + + s = createSineStream(sampleRate, time, 0, false, false, makePartition); + TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); + delete s; + + // 11111 Hz tests + sampleRate = 11111; + s = createSineStream(sampleRate, time, 0, false, false, makePartition); + TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); + delete s; + + s = createSineStream(sampleRate, time, 0, false, false, makePartition); + TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); + delete s; + + // 22050 Hz stereo test + sampleRate = 22050; + s = createSineStream(sampleRate, time, 0, false, true, makePartition); + TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); + delete s; + + s = createSineStream(sampleRate, time, 0, true, true, makePartition); + TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); + delete s; + } + +public: + void test_length() { + lengthTest(false); + } + + void test_length_parted() { + lengthTest(true); + } + +private: + void seekTest(const int sampleRate, const int time, const bool isStereo, const bool makePartition) { + const int totalFrames = sampleRate * time * (isStereo ? 2 : 1); + int readData = 0, offset = 0; + + int16 *buffer = new int16[totalFrames]; + Audio::SeekableAudioStream *s = 0; + int16 *sine = 0; + + s = createSineStream(sampleRate, time, &sine, false, isStereo, makePartition); + + // Seek to 500ms + const Audio::Timestamp a(0, 1, 2); + offset = Audio::convertTimeToStreamPos(a, sampleRate, isStereo).totalNumberOfFrames(); + readData = totalFrames - offset; + + TS_ASSERT_EQUALS(s->seek(a), true); + TS_ASSERT_EQUALS(s->endOfData(), false); + TS_ASSERT_EQUALS(s->readBuffer(buffer, readData), readData); + TS_ASSERT_EQUALS(memcmp(buffer, sine + offset, readData * sizeof(int16)), 0); + TS_ASSERT_EQUALS(s->endOfData(), true); + + // Seek to 3/4 of a second + const Audio::Timestamp b(0, 3, 4); + offset = Audio::convertTimeToStreamPos(b, sampleRate, isStereo).totalNumberOfFrames(); + readData = totalFrames - offset; + + TS_ASSERT_EQUALS(s->seek(b), true); + TS_ASSERT_EQUALS(s->endOfData(), false); + TS_ASSERT_EQUALS(s->readBuffer(buffer, readData), readData); + TS_ASSERT_EQUALS(memcmp(buffer, sine + offset, readData * sizeof(int16)), 0); + TS_ASSERT_EQUALS(s->endOfData(), true); + + // Seek to the start of the stream + TS_ASSERT_EQUALS(s->seek(0), true); + TS_ASSERT_EQUALS(s->endOfData(), false); + + // Seek to the mid of the stream + const Audio::Timestamp c(time * 1000 / 2, 1000); + offset = Audio::convertTimeToStreamPos(c, sampleRate, isStereo).totalNumberOfFrames(); + readData = totalFrames - offset; + + TS_ASSERT_EQUALS(s->seek(c), true); + TS_ASSERT_EQUALS(s->endOfData(), false); + TS_ASSERT_EQUALS(s->readBuffer(buffer, readData), readData); + TS_ASSERT_EQUALS(memcmp(buffer, sine + offset, readData * sizeof(int16)), 0); + TS_ASSERT_EQUALS(s->endOfData(), true); + + // Seek to the 1/4th of the last second of the stream + const Audio::Timestamp d(time - 1, 1, 4); + offset = Audio::convertTimeToStreamPos(d, sampleRate, isStereo).totalNumberOfFrames(); + readData = totalFrames - offset; + + TS_ASSERT_EQUALS(s->seek(d), true); + TS_ASSERT_EQUALS(s->endOfData(), false); + TS_ASSERT_EQUALS(s->readBuffer(buffer, readData), readData); + TS_ASSERT_EQUALS(memcmp(buffer, sine + offset, readData * sizeof(int16)), 0); + TS_ASSERT_EQUALS(s->endOfData(), true); + + // Try to seek after the end of the stream + TS_ASSERT_EQUALS(s->seek(Audio::Timestamp(time, 1, 100000)), false); + TS_ASSERT_EQUALS(s->endOfData(), true); + + // Try to seek exactly at the end of the stream + TS_ASSERT_EQUALS(s->seek(Audio::Timestamp(time * 1000, 1000)), true); + TS_ASSERT_EQUALS(s->endOfData(), true); + + delete[] sine; + delete s; + delete[] buffer; + } + +public: + void test_seek_mono() { + seekTest(11025, 2, false, false); + } + + void test_seek_stereo() { + seekTest(11025, 2, true, false); + } + + void test_seek_mono_parted() { + seekTest(11025, 2, false, true); + } + + void test_seek_stereo_parted() { + seekTest(11025, 2, true, true); + } +}; diff --git a/test/audio/timestamp.h b/test/audio/timestamp.h new file mode 100644 index 0000000000..ca56e34a4d --- /dev/null +++ b/test/audio/timestamp.h @@ -0,0 +1,241 @@ +#include + +#include "audio/timestamp.h" + +class TimestampTestSuite : public CxxTest::TestSuite +{ + public: + void test_diff_add_frames() { + const Audio::Timestamp a(10000, 1000); + const Audio::Timestamp b(10001, 1000); + const Audio::Timestamp c(10002, 1000); + + TS_ASSERT_EQUALS(a.frameDiff(b), -1); + TS_ASSERT_EQUALS(b.frameDiff(a), 1); + TS_ASSERT_EQUALS(c.frameDiff(a), 2); + TS_ASSERT_EQUALS(b.addFrames(2000).frameDiff(a), 2001); + TS_ASSERT_EQUALS(a.frameDiff(b), -1); + TS_ASSERT_EQUALS(b.frameDiff(a), 1); + TS_ASSERT_EQUALS(c.frameDiff(a), 2); + TS_ASSERT_EQUALS(b.addFrames(2000).frameDiff(a.addFrames(-1000)), 3001); + TS_ASSERT_EQUALS(a.frameDiff(b), -1); + TS_ASSERT_EQUALS(b.frameDiff(a), 1); + TS_ASSERT_EQUALS(c.frameDiff(a), 2); + } + + void test_diff_add_msecs() { + Audio::Timestamp ts0(3, 22050); + Audio::Timestamp ts1(0, 22050); + Audio::Timestamp ts2(0, 22050); + + TS_ASSERT_EQUALS(ts0.msecs(), 3); + TS_ASSERT_EQUALS(ts0.totalNumberOfFrames(), 3 * 22050 / 1000); + TS_ASSERT_EQUALS(ts0.numberOfFrames(), 3 * 22050 / 1000); + + ts1 = ts1.addFrames(53248); + TS_ASSERT_EQUALS(ts1.secs(), 2); + TS_ASSERT_EQUALS(ts1.msecs(), 53248 * 1000 / 22050); + TS_ASSERT_EQUALS(ts1.totalNumberOfFrames(), 53248); + TS_ASSERT_EQUALS(ts1.numberOfFrames(), 53248 - 2 * 22050); + ts1 = ts1.addMsecs(47); + TS_ASSERT_EQUALS(ts1.secs(), 2); + TS_ASSERT_EQUALS(ts1.msecs(), 2414+47); + TS_ASSERT_EQUALS(ts1.totalNumberOfFrames(), 47*22050 / 1000 + 53248); + TS_ASSERT_EQUALS(ts1.numberOfFrames(), 47*22050 / 1000 + 53248 - 2 * 22050); + + ts2 = ts2.addMsecs(47); + TS_ASSERT_EQUALS(ts2.secs(), 0); + TS_ASSERT_EQUALS(ts2.msecs(), 47); + TS_ASSERT_EQUALS(ts2.totalNumberOfFrames(), 47*22050 / 1000); + TS_ASSERT_EQUALS(ts2.numberOfFrames(), 47*22050 / 1000); + ts2 = ts2.addFrames(53248); + TS_ASSERT_EQUALS(ts2.secs(), 2); + TS_ASSERT_EQUALS(ts2.msecs(), 2414+47); + TS_ASSERT_EQUALS(ts2.totalNumberOfFrames(), 47*22050 / 1000 + 53248); + TS_ASSERT_EQUALS(ts2.numberOfFrames(), 47*22050 / 1000 + 53248 - 2 * 22050); + } + + void test_ticks() { + const Audio::Timestamp a(1234, 60); + const Audio::Timestamp b(5678, 60); + + TS_ASSERT_EQUALS(a.msecs(), 1234); + TS_ASSERT_EQUALS(b.msecs(), 5678); + + TS_ASSERT_EQUALS(a.secs(), 1); + TS_ASSERT_EQUALS(b.secs(), 5); + + TS_ASSERT_EQUALS(a.msecsDiff(b), 1234 - 5678); + TS_ASSERT_EQUALS(b.msecsDiff(a), 5678 - 1234); + + TS_ASSERT_EQUALS(a.frameDiff(b), (1234 - 5678) * 60 / 1000); + TS_ASSERT_EQUALS(b.frameDiff(a), (5678 - 1234) * 60 / 1000); + + TS_ASSERT_EQUALS(a.addFrames(1).msecs(), (1234 + 1000 * 1/60)); + TS_ASSERT_EQUALS(a.addFrames(59).msecs(), (1234 + 1000 * 59/60)); + TS_ASSERT_EQUALS(a.addFrames(60).msecs(), (1234 + 1000 * 60/60)); + + // As soon as we go back even by only one frame, the msec value + // has to drop by at least one. + TS_ASSERT_EQUALS(a.addFrames(-1).msecs(), (1234 - 1000 * 1/60 - 1)); + TS_ASSERT_EQUALS(a.addFrames(-59).msecs(), (1234 - 1000 * 59/60 - 1)); + TS_ASSERT_EQUALS(a.addFrames(-60).msecs(), (1234 - 1000 * 60/60)); + } + + void test_more_add_diff() { + const Audio::Timestamp c(10002, 1000); + + for (int i = -10000; i < 10000; i++) { + int v = c.addFrames(i).frameDiff(c); + TS_ASSERT_EQUALS(v, i); + } + } + + void test_negate() { + const Audio::Timestamp a = Audio::Timestamp(0, 60).addFrames(13); + const Audio::Timestamp b = -a; + + TS_ASSERT_EQUALS(a.msecs() + 1, -b.msecs()); + TS_ASSERT_EQUALS(a.totalNumberOfFrames(), -b.totalNumberOfFrames()); + TS_ASSERT_EQUALS(a.numberOfFrames(), 60 - b.numberOfFrames()); + } + + void test_add_sub() { + const Audio::Timestamp a = Audio::Timestamp(0, 60).addFrames(13); + const Audio::Timestamp b = -a; + const Audio::Timestamp c = Audio::Timestamp(0, 60).addFrames(20); + + TS_ASSERT_EQUALS((a+a).secs(), 0); + TS_ASSERT_EQUALS((a+a).numberOfFrames(), 2*13); + + TS_ASSERT_EQUALS((a+b).secs(), 0); + TS_ASSERT_EQUALS((a+b).numberOfFrames(), 0); + + TS_ASSERT_EQUALS((a+c).secs(), 0); + TS_ASSERT_EQUALS((a+c).numberOfFrames(), 13+20); + + TS_ASSERT_EQUALS((a-a).secs(), 0); + TS_ASSERT_EQUALS((a-a).numberOfFrames(), 0); + + TS_ASSERT_EQUALS((a-b).secs(), 0); + TS_ASSERT_EQUALS((a-b).numberOfFrames(), 2*13); + + TS_ASSERT_EQUALS((a-c).secs(), -1); + TS_ASSERT_EQUALS((a-c).numberOfFrames(), 60 + (13 - 20)); + } + + void test_diff_with_conversion() { + const Audio::Timestamp a = Audio::Timestamp(10, 1000).addFrames(20); + const Audio::Timestamp b = Audio::Timestamp(10, 1000/5).addFrames(20/5); + const Audio::Timestamp c = Audio::Timestamp(10, 1000*2).addFrames(20*2); + + TS_ASSERT_EQUALS(a.frameDiff(a), 0); + TS_ASSERT_EQUALS(a.frameDiff(b), 0); + TS_ASSERT_EQUALS(a.frameDiff(c), 0); + + TS_ASSERT_EQUALS(b.frameDiff(a), 0); + TS_ASSERT_EQUALS(b.frameDiff(b), 0); + TS_ASSERT_EQUALS(b.frameDiff(c), 0); + + TS_ASSERT_EQUALS(c.frameDiff(a), 0); + TS_ASSERT_EQUALS(c.frameDiff(b), 0); + TS_ASSERT_EQUALS(c.frameDiff(c), 0); + } + + + void test_convert() { + const Audio::Timestamp a = Audio::Timestamp(10, 1000).addFrames(20); + const Audio::Timestamp b = Audio::Timestamp(10, 1000/5).addFrames(20/5); + const Audio::Timestamp c = Audio::Timestamp(10, 1000*2).addFrames(20*2); + + TS_ASSERT_EQUALS(a.convertToFramerate(1000/5), b); + TS_ASSERT_EQUALS(a.convertToFramerate(1000*2), c); + } + + void test_equals() { + const Audio::Timestamp a = Audio::Timestamp(500, 1000); + Audio::Timestamp b = Audio::Timestamp(0, 1000); + Audio::Timestamp c = Audio::Timestamp(0, 100); + + TS_ASSERT_EQUALS(a, Audio::Timestamp(0, 500, 1000)); + + TS_ASSERT(a != b); + TS_ASSERT(a != c); + TS_ASSERT(b == c); + + b = b.addFrames(500); + c = c.addFrames(50); + + TS_ASSERT(a == b); + TS_ASSERT(a == c); + TS_ASSERT(b == c); + } + + + void test_compare() { + const Audio::Timestamp a = Audio::Timestamp(60, 1000); + Audio::Timestamp b = Audio::Timestamp(60, 60); + Audio::Timestamp c = Audio::Timestamp(60, 44100); + + TS_ASSERT(a <= b); + TS_ASSERT(b <= c); + TS_ASSERT(a <= c); + + TS_ASSERT(b >= a); + TS_ASSERT(c >= b); + TS_ASSERT(c >= a); + + b = b.addFrames(60 / 12); + c = c.addFrames(44100 / 10); + + TS_ASSERT(a < b); + TS_ASSERT(b < c); + TS_ASSERT(a < c); + + TS_ASSERT(b > a); + TS_ASSERT(c > b); + TS_ASSERT(c > a); + + TS_ASSERT(a <= b); + TS_ASSERT(b <= c); + TS_ASSERT(a <= c); + + TS_ASSERT(b >= a); + TS_ASSERT(c >= b); + TS_ASSERT(c >= a); + } + + + void test_framerate() { + const Audio::Timestamp a = Audio::Timestamp(500, 1000); + const Audio::Timestamp b = Audio::Timestamp(500, 67); + const Audio::Timestamp c = Audio::Timestamp(500, 100); + const Audio::Timestamp d = Audio::Timestamp(500, 44100); + + TS_ASSERT_EQUALS(a.framerate(), (uint)1000); + TS_ASSERT_EQUALS(b.framerate(), (uint)67); + TS_ASSERT_EQUALS(c.framerate(), (uint)100); + TS_ASSERT_EQUALS(d.framerate(), (uint)44100); + } + + void test_direct_query() { + const Audio::Timestamp a = Audio::Timestamp(0, 22050); + const Audio::Timestamp b = a.addFrames(11025); + const Audio::Timestamp c = Audio::Timestamp(1500, 22050); + + TS_ASSERT_EQUALS(a.secs(), 0); + TS_ASSERT_EQUALS(a.msecs(), 0); + TS_ASSERT_EQUALS(a.numberOfFrames(), 0); + TS_ASSERT_EQUALS(a.totalNumberOfFrames(), 0); + + TS_ASSERT_EQUALS(b.secs(), 0); + TS_ASSERT_EQUALS(b.msecs(), 500); + TS_ASSERT_EQUALS(b.numberOfFrames(), 11025); + TS_ASSERT_EQUALS(b.totalNumberOfFrames(), 11025); + + TS_ASSERT_EQUALS(c.secs(), 1); + TS_ASSERT_EQUALS(c.msecs(), 1500); + TS_ASSERT_EQUALS(c.numberOfFrames(), 11025); + TS_ASSERT_EQUALS(c.totalNumberOfFrames(), 33075); + } +}; diff --git a/test/module.mk b/test/module.mk index 20425b6359..3542ae2903 100644 --- a/test/module.mk +++ b/test/module.mk @@ -5,8 +5,8 @@ # ###################################################################### -TESTS := $(srcdir)/test/common/*.h $(srcdir)/test/sound/*.h -TEST_LIBS := sound/libsound.a common/libcommon.a +TESTS := $(srcdir)/test/common/*.h $(srcdir)/test/audio/*.h +TEST_LIBS := audio/libaudio.a common/libcommon.a # TEST_FLAGS := --runner=StdioPrinter diff --git a/test/sound/audiostream.h b/test/sound/audiostream.h deleted file mode 100644 index ba3adcb34c..0000000000 --- a/test/sound/audiostream.h +++ /dev/null @@ -1,209 +0,0 @@ -#include - -#include "sound/audiostream.h" - -#include "helper.h" - -class AudioStreamTestSuite : public CxxTest::TestSuite -{ -public: - void test_convertTimeToStreamPos() { - const Audio::Timestamp a = Audio::convertTimeToStreamPos(Audio::Timestamp(500, 1000), 11025, true); - // The last bit has to be 0 in any case for a stereo stream. - TS_ASSERT_EQUALS(a.totalNumberOfFrames() & 1, 0); - - // TODO: This test is rather hacky... actually converTimeToStreamPos might also return 11026 - // instead of 11024 and it would still be a valid sample position for ~500ms. - TS_ASSERT_EQUALS(a.totalNumberOfFrames(), 11024); - - const Audio::Timestamp b = Audio::convertTimeToStreamPos(Audio::Timestamp(500, 1000), 11025, false); - TS_ASSERT_EQUALS(b.totalNumberOfFrames(), 500 * 11025 / 1000); - - // Test Audio CD positioning - - // for 44kHz and stereo - const Audio::Timestamp c = Audio::convertTimeToStreamPos(Audio::Timestamp(0, 50, 75), 44100, true); - TS_ASSERT_EQUALS(c.totalNumberOfFrames(), 50 * 44100 * 2 / 75); - - // for 11kHz and mono - const Audio::Timestamp d = Audio::convertTimeToStreamPos(Audio::Timestamp(0, 50, 75), 11025, false); - TS_ASSERT_EQUALS(d.totalNumberOfFrames(), 50 * 11025 / 75); - - // Some misc test - const Audio::Timestamp e = Audio::convertTimeToStreamPos(Audio::Timestamp(1, 1, 4), 11025, false); - TS_ASSERT_EQUALS(e.totalNumberOfFrames(), 5 * 11025 / 4); - } - -private: - void testLoopingAudioStreamFixedIter(const int sampleRate, const bool isStereo) { - const int secondLength = sampleRate * (isStereo ? 2 : 1); - - int16 *sine = 0; - Audio::SeekableAudioStream *s = createSineStream(sampleRate, 1, &sine, false, isStereo); - Audio::LoopingAudioStream *loop = new Audio::LoopingAudioStream(s, 7); - - int16 *buffer = new int16[secondLength * 3]; - - // Check parameters - TS_ASSERT_EQUALS(loop->isStereo(), isStereo); - TS_ASSERT_EQUALS(loop->getRate(), sampleRate); - TS_ASSERT_EQUALS(loop->endOfData(), false); - TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)0); - - // Read one second - TS_ASSERT_EQUALS(loop->readBuffer(buffer, secondLength), secondLength); - TS_ASSERT_EQUALS(memcmp(buffer, sine, secondLength * sizeof(int16)), 0); - - TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)1); - TS_ASSERT_EQUALS(loop->endOfData(), false); - - // Read two seconds - TS_ASSERT_EQUALS(loop->readBuffer(buffer, secondLength * 2), secondLength * 2); - TS_ASSERT_EQUALS(memcmp(buffer, sine, secondLength * sizeof(int16)), 0); - TS_ASSERT_EQUALS(memcmp(buffer + secondLength, sine, secondLength * sizeof(int16)), 0); - - TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)3); - TS_ASSERT_EQUALS(loop->endOfData(), false); - - // Read three seconds - TS_ASSERT_EQUALS(loop->readBuffer(buffer, secondLength * 3), secondLength * 3); - TS_ASSERT_EQUALS(memcmp(buffer, sine, secondLength * sizeof(int16)), 0); - TS_ASSERT_EQUALS(memcmp(buffer + secondLength, sine, secondLength * sizeof(int16)), 0); - TS_ASSERT_EQUALS(memcmp(buffer + secondLength * 2, sine, secondLength * sizeof(int16)), 0); - - TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)6); - TS_ASSERT_EQUALS(loop->endOfData(), false); - - // Read the last second in two parts - const int firstStep = secondLength / 2; - const int secondStep = secondLength - firstStep; - - TS_ASSERT_EQUALS(loop->readBuffer(buffer, firstStep), firstStep); - TS_ASSERT_EQUALS(memcmp(buffer, sine, firstStep * sizeof(int16)), 0); - - TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)6); - TS_ASSERT_EQUALS(loop->endOfData(), false); - - TS_ASSERT_EQUALS(loop->readBuffer(buffer, secondLength), secondStep); - TS_ASSERT_EQUALS(memcmp(buffer, sine + firstStep, secondStep * sizeof(int16)), 0); - - TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)7); - TS_ASSERT_EQUALS(loop->endOfData(), true); - - // Try to read beyond the end of the stream - TS_ASSERT_EQUALS(loop->readBuffer(buffer, secondLength), 0); - TS_ASSERT_EQUALS(loop->getCompleteIterations(), (uint)7); - TS_ASSERT_EQUALS(loop->endOfData(), true); - - delete[] buffer; - delete loop; - delete[] sine; - } - -public: - void test_looping_audio_stream_mono_11025_fixed_iter() { - testLoopingAudioStreamFixedIter(11025, false); - } - - void test_looping_audio_stream_mono_22050_fixed_iter() { - testLoopingAudioStreamFixedIter(22050, false); - } - - void test_looping_audio_stream_stereo_11025_fixed_iter() { - testLoopingAudioStreamFixedIter(11025, true); - } - - void test_looping_audio_stream_stereo_22050_fixed_iter() { - testLoopingAudioStreamFixedIter(22050, true); - } - -private: - void testSubLoopingAudioStreamFixedIter(const int sampleRate, const bool isStereo, const int time, const int loopEndTime) { - const int secondLength = sampleRate * (isStereo ? 2 : 1); - - const Audio::Timestamp loopStart(500, 1000), loopEnd(loopEndTime * 1000, 1000); - - const int32 loopStartPos = Audio::convertTimeToStreamPos(loopStart, sampleRate, isStereo).totalNumberOfFrames(); - const int32 loopEndPos = Audio::convertTimeToStreamPos(loopEnd, sampleRate, isStereo).totalNumberOfFrames(); - - const int32 loopIteration = loopEndPos - loopStartPos; - - int16 *sine = 0; - Audio::SeekableAudioStream *s = createSineStream(sampleRate, time, &sine, false, isStereo); - Audio::SubLoopingAudioStream *loop = new Audio::SubLoopingAudioStream(s, 5, loopStart, loopEnd); - - const int32 bufferLen = MAX(loopIteration * 3, loopEndPos); - int16 *buffer = new int16[bufferLen]; - - // Check parameters - TS_ASSERT_EQUALS(loop->isStereo(), isStereo); - TS_ASSERT_EQUALS(loop->getRate(), sampleRate); - TS_ASSERT_EQUALS(loop->endOfData(), false); - - // Read the non-looped part + one iteration - TS_ASSERT_EQUALS(loop->readBuffer(buffer, loopEndPos), loopEndPos); - TS_ASSERT_EQUALS(memcmp(buffer, sine, loopEndPos * sizeof(int16)), 0); - TS_ASSERT_EQUALS(loop->endOfData(), false); - - // We should have one full iteration now - - // Read another loop iteration - TS_ASSERT_EQUALS(loop->readBuffer(buffer, loopIteration), loopIteration); - TS_ASSERT_EQUALS(memcmp(buffer, sine + loopStartPos, loopIteration * sizeof(int16)), 0); - TS_ASSERT_EQUALS(loop->endOfData(), false); - - // We should have two full iterations now - - // Read three loop iterations at once - TS_ASSERT_EQUALS(loop->readBuffer(buffer, loopIteration * 3), loopIteration * 3); - TS_ASSERT_EQUALS(memcmp(buffer + loopIteration * 0, sine + loopStartPos, loopIteration * sizeof(int16)), 0); - TS_ASSERT_EQUALS(memcmp(buffer + loopIteration * 1, sine + loopStartPos, loopIteration * sizeof(int16)), 0); - TS_ASSERT_EQUALS(memcmp(buffer + loopIteration * 2, sine + loopStartPos, loopIteration * sizeof(int16)), 0); - TS_ASSERT_EQUALS(loop->endOfData(), true); - - // We should have five full iterations now, thus the stream should be done - - // Try to read beyond the end of the stream (note this only applies, till we define that SubLoopingAudioStream should - // stop playing after the looped area). - TS_ASSERT_EQUALS(loop->readBuffer(buffer, secondLength), 0); - TS_ASSERT_EQUALS(loop->endOfData(), true); - - delete[] buffer; - delete loop; - delete[] sine; - } - -public: - void test_sub_looping_audio_stream_mono_11025_mid_fixed_iter() { - testSubLoopingAudioStreamFixedIter(11025, false, 2, 1); - } - - void test_sub_looping_audio_stream_mono_22050_mid_fixed_iter() { - testSubLoopingAudioStreamFixedIter(22050, false, 2, 1); - } - - void test_sub_looping_audio_stream_stereo_11025_mid_fixed_iter() { - testSubLoopingAudioStreamFixedIter(11025, true, 2, 1); - } - - void test_sub_looping_audio_stream_stereo_22050_mid_fixed_iter() { - testSubLoopingAudioStreamFixedIter(22050, true, 2, 1); - } - - void test_sub_looping_audio_stream_mono_11025_end_fixed_iter() { - testSubLoopingAudioStreamFixedIter(11025, false, 2, 2); - } - - void test_sub_looping_audio_stream_mono_22050_end_fixed_iter() { - testSubLoopingAudioStreamFixedIter(22050, false, 2, 2); - } - - void test_sub_looping_audio_stream_stereo_11025_end_fixed_iter() { - testSubLoopingAudioStreamFixedIter(11025, true, 2, 2); - } - - void test_sub_looping_audio_stream_stereo_22050_end_fixed_iter() { - testSubLoopingAudioStreamFixedIter(22050, true, 2, 2); - } -}; - diff --git a/test/sound/helper.h b/test/sound/helper.h deleted file mode 100644 index 394287c884..0000000000 --- a/test/sound/helper.h +++ /dev/null @@ -1,112 +0,0 @@ -#ifndef TEST_SOUND_HELPER_H -#define TEST_SOUND_HELPER_H - -#include "sound/decoders/raw.h" - -#include "common/stream.h" -#include "common/endian.h" - -#include -#include - -template -static T *createSine(const int sampleRate, const int time) { - T *sine = (T *)malloc(sizeof(T) * time * sampleRate); - - const bool isUnsigned = !std::numeric_limits::is_signed; - const T xorMask = isUnsigned ? (1 << (std::numeric_limits::digits - 1)) : 0; - const T maxValue = std::numeric_limits::max() ^ xorMask; - - for (int i = 0; i < time * sampleRate; ++i) - sine[i] = ((T)(sin((double)i / sampleRate * 2 * PI) * maxValue)) ^ xorMask; - - return sine; -} - -template -static Common::SeekableReadStream *createPartitionStream(T *sine, const int samples, Audio::RawStreamBlockList &blockList) { - const int block1Len = samples / 2; - const int block1Size = block1Len * sizeof(T); - const int block2Len = samples - block1Len; - const int block2Size = block2Len * sizeof(T); - - const int bufferLen = samples * 2; - const int bufferSize = bufferLen * sizeof(T); - T *partition = (T *)calloc(1, bufferSize); - - Audio::RawStreamBlock block; - - // The will layout the buffer like the following: - // [Zero], [Part2], [Zero], [Part1] - - // The first part of the stream is at the end of the memory buffer - block.pos = bufferSize - block1Size; - block.len = block1Len; - memcpy(partition + bufferLen - block1Len, sine, block1Size); - blockList.push_back(block); - - // The second part of the stream is near the beginning of the memory buffer - block.pos = block2Size; - block.len = block2Len; - memcpy(partition + block2Len, sine + block1Len, block2Size); - blockList.push_back(block); - - free(sine); - - return new Common::MemoryReadStream((const byte *)partition, bufferSize, DisposeAfterUse::YES); -} - -template -static Audio::SeekableAudioStream *createSineStream(const int sampleRate, const int time, int16 **comp, bool le, bool isStereo, bool makePartition = false) { - T *sine = createSine(sampleRate, time * (isStereo ? 2 : 1)); - - const bool isUnsigned = !std::numeric_limits::is_signed; - const T xorMask = isUnsigned ? (1 << (std::numeric_limits::digits - 1)) : 0; - const bool is16Bits = (sizeof(T) == 2); - assert(sizeof(T) == 2 || sizeof(T) == 1); - - const int samples = sampleRate * time * (isStereo ? 2 : 1); - - if (comp) { - *comp = new int16[samples]; - for (int i = 0; i < samples; ++i) { - if (is16Bits) - (*comp)[i] = sine[i] ^ xorMask; - else - (*comp)[i] = (sine[i] ^ xorMask) << 8; - } - } - - if (is16Bits) { - if (le) { - for (int i = 0; i < samples; ++i) - WRITE_LE_UINT16(&sine[i], sine[i]); - } else { - for (int i = 0; i < samples; ++i) - WRITE_BE_UINT16(&sine[i], sine[i]); - } - } - - Audio::SeekableAudioStream *s = 0; - if (makePartition) { - Audio::RawStreamBlockList blockList; - Common::SeekableReadStream *sD = createPartitionStream(sine, samples, blockList); - s = Audio::makeRawStream(sD, blockList, sampleRate, - (is16Bits ? Audio::FLAG_16BITS : 0) - | (isUnsigned ? Audio::FLAG_UNSIGNED : 0) - | (le ? Audio::FLAG_LITTLE_ENDIAN : 0) - | (isStereo ? Audio::FLAG_STEREO : 0)); - } else { - Common::SeekableReadStream *sD = new Common::MemoryReadStream((const byte *)sine, sizeof(T) * samples, DisposeAfterUse::YES); - s = Audio::makeRawStream(sD, sampleRate, - (is16Bits ? Audio::FLAG_16BITS : 0) - | (isUnsigned ? Audio::FLAG_UNSIGNED : 0) - | (le ? Audio::FLAG_LITTLE_ENDIAN : 0) - | (isStereo ? Audio::FLAG_STEREO : 0)); - } - - return s; -} - -#endif - diff --git a/test/sound/raw.h b/test/sound/raw.h deleted file mode 100644 index b8f7b2dae6..0000000000 --- a/test/sound/raw.h +++ /dev/null @@ -1,358 +0,0 @@ -#include - -#include "sound/decoders/raw.h" - -#include "helper.h" - -class RawStreamTestSuite : public CxxTest::TestSuite -{ -private: - template - void readBufferTestTemplate(const int sampleRate, const int time, const bool le, const bool isStereo, const bool makePartition = false) { - int16 *sine; - Audio::SeekableAudioStream *s = createSineStream(sampleRate, time, &sine, le, isStereo, makePartition); - - const int totalSamples = sampleRate * time * (isStereo ? 2 : 1); - int16 *buffer = new int16[totalSamples]; - TS_ASSERT_EQUALS(s->readBuffer(buffer, totalSamples), totalSamples); - TS_ASSERT_EQUALS(memcmp(sine, buffer, sizeof(int16) * totalSamples), 0); - TS_ASSERT_EQUALS(s->endOfData(), true); - - delete[] sine; - delete[] buffer; - delete s; - } - -public: - void test_read_buffer_8_bit_signed_mono() { - readBufferTestTemplate(11025, 2, false, false); - } - - void test_read_buffer_8_bit_signed_stereo() { - readBufferTestTemplate(11025, 2, false, true); - } - - void test_read_buffer_8_bit_unsigned_mono() { - readBufferTestTemplate(11025, 2, false, false); - } - - void test_read_buffer_16_bit_signed_be_mono() { - readBufferTestTemplate(11025, 2, false, false); - } - - void test_read_buffer_16_bit_signed_be_stereo() { - readBufferTestTemplate(11025, 2, false, true); - } - - void test_read_buffer_16_bit_unsigned_be_mono() { - readBufferTestTemplate(11025, 2, false, false); - } - - void test_read_buffer_16_bit_unsigned_be_stereo() { - readBufferTestTemplate(11025, 2, false, true); - } - - void test_read_buffer_16_bit_signed_le_mono() { - readBufferTestTemplate(11025, 2, true, false); - } - - void test_read_buffer_16_bit_signed_le_stereo() { - readBufferTestTemplate(11025, 2, true, true); - } - - void test_read_buffer_16_bit_unsigned_le_mono() { - readBufferTestTemplate(11025, 2, true, false); - } - - void test_read_buffer_16_bit_unsigned_le_stereo() { - readBufferTestTemplate(11025, 2, true, true); - } - - void test_read_buffer_8_bit_signed_mono_parted() { - readBufferTestTemplate(11025, 2, false, false, true); - } - - void test_read_buffer_8_bit_signed_stereo_parted() { - readBufferTestTemplate(11025, 2, false, true, true); - } - - void test_read_buffer_8_bit_unsigned_mono_parted() { - readBufferTestTemplate(11025, 2, false, false, true); - } - - void test_read_buffer_16_bit_signed_be_mono_parted() { - readBufferTestTemplate(11025, 2, false, false, true); - } - - void test_read_buffer_16_bit_signed_be_stereo_parted() { - readBufferTestTemplate(11025, 2, false, true, true); - } - - void test_read_buffer_16_bit_unsigned_be_mono_parted() { - readBufferTestTemplate(11025, 2, false, false, true); - } - - void test_read_buffer_16_bit_unsigned_be_stereo_parted() { - readBufferTestTemplate(11025, 2, false, true, true); - } - - void test_read_buffer_16_bit_signed_le_mono_parted() { - readBufferTestTemplate(11025, 2, true, false, true); - } - - void test_read_buffer_16_bit_signed_le_stereo_parted() { - readBufferTestTemplate(11025, 2, true, true, true); - } - - void test_read_buffer_16_bit_unsigned_le_mono_parted() { - readBufferTestTemplate(11025, 2, true, false, true); - } - - void test_read_buffer_16_bit_unsigned_le_stereo_parted() { - readBufferTestTemplate(11025, 2, true, true, true); - } - -private: - void partialReadTest(const bool makePartition) { - const int sampleRate = 11025; - const int time = 4; - - int16 *sine; - Audio::SeekableAudioStream *s = createSineStream(sampleRate, time, &sine, false, false, makePartition); - int16 *buffer = new int16[sampleRate * time]; - - TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate), sampleRate); - TS_ASSERT_EQUALS(memcmp(sine, buffer, sampleRate), 0); - TS_ASSERT_EQUALS(s->endOfData(), false); - - TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate * 2), sampleRate * 2); - TS_ASSERT_EQUALS(memcmp(sine + sampleRate, buffer, sampleRate * 2), 0); - TS_ASSERT_EQUALS(s->endOfData(), false); - - TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate), sampleRate); - TS_ASSERT_EQUALS(memcmp(sine + sampleRate * 3, buffer, sampleRate), 0); - TS_ASSERT_EQUALS(s->endOfData(), true); - - delete[] sine; - delete[] buffer; - delete s; - } -public: - void test_partial_read() { - partialReadTest(false); - } - - void test_partial_read_parted() { - partialReadTest(true); - } - -private: - void readAfterEndTest(const bool makePartition) { - const int sampleRate = 11025; - const int time = 1; - Audio::SeekableAudioStream *s = createSineStream(sampleRate, time, 0, false, false); - int16 *buffer = new int16[sampleRate * time]; - - TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate * time), sampleRate * time); - TS_ASSERT_EQUALS(s->endOfData(), true); - - TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate * time), 0); - TS_ASSERT_EQUALS(s->endOfData(), true); - - delete[] buffer; - delete s; - } - -public: - void test_read_after_end() { - readAfterEndTest(false); - } - - void test_read_after_end_parted() { - readAfterEndTest(true); - } - -private: - void rewindTest(const bool makePartition) { - const int sampleRate = 11025; - const int time = 2; - Audio::SeekableAudioStream *s = createSineStream(sampleRate, time, 0, false, false, makePartition); - int16 *buffer = new int16[sampleRate * time]; - - TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate * time), sampleRate * time); - TS_ASSERT_EQUALS(s->endOfData(), true); - - s->rewind(); - TS_ASSERT_EQUALS(s->endOfData(), false); - - TS_ASSERT_EQUALS(s->readBuffer(buffer, sampleRate * time), sampleRate * time); - TS_ASSERT_EQUALS(s->endOfData(), true); - - delete[] buffer; - delete s; - } -public: - void test_rewind() { - rewindTest(false); - } - - void test_rewind_parted() { - rewindTest(true); - } - -private: - void lengthTest(const bool makePartition) { - int sampleRate = 0; - const int time = 4; - - Audio::SeekableAudioStream *s = 0; - - // 11025 Hz tests - sampleRate = 11025; - s = createSineStream(sampleRate, time, 0, false, false, makePartition); - TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); - delete s; - - s = createSineStream(sampleRate, time, 0, false, false, makePartition); - TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); - delete s; - - // 48000 Hz tests - sampleRate = 48000; - s = createSineStream(sampleRate, time, 0, false, false, makePartition); - TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); - delete s; - - s = createSineStream(sampleRate, time, 0, true, false, makePartition); - TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); - delete s; - - // 11840 Hz tests - sampleRate = 11840; - s = createSineStream(sampleRate, time, 0, false, false, makePartition); - TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); - delete s; - - s = createSineStream(sampleRate, time, 0, false, false, makePartition); - TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); - delete s; - - // 11111 Hz tests - sampleRate = 11111; - s = createSineStream(sampleRate, time, 0, false, false, makePartition); - TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); - delete s; - - s = createSineStream(sampleRate, time, 0, false, false, makePartition); - TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); - delete s; - - // 22050 Hz stereo test - sampleRate = 22050; - s = createSineStream(sampleRate, time, 0, false, true, makePartition); - TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); - delete s; - - s = createSineStream(sampleRate, time, 0, true, true, makePartition); - TS_ASSERT_EQUALS(s->getLength().totalNumberOfFrames(), sampleRate * time); - delete s; - } - -public: - void test_length() { - lengthTest(false); - } - - void test_length_parted() { - lengthTest(true); - } - -private: - void seekTest(const int sampleRate, const int time, const bool isStereo, const bool makePartition) { - const int totalFrames = sampleRate * time * (isStereo ? 2 : 1); - int readData = 0, offset = 0; - - int16 *buffer = new int16[totalFrames]; - Audio::SeekableAudioStream *s = 0; - int16 *sine = 0; - - s = createSineStream(sampleRate, time, &sine, false, isStereo, makePartition); - - // Seek to 500ms - const Audio::Timestamp a(0, 1, 2); - offset = Audio::convertTimeToStreamPos(a, sampleRate, isStereo).totalNumberOfFrames(); - readData = totalFrames - offset; - - TS_ASSERT_EQUALS(s->seek(a), true); - TS_ASSERT_EQUALS(s->endOfData(), false); - TS_ASSERT_EQUALS(s->readBuffer(buffer, readData), readData); - TS_ASSERT_EQUALS(memcmp(buffer, sine + offset, readData * sizeof(int16)), 0); - TS_ASSERT_EQUALS(s->endOfData(), true); - - // Seek to 3/4 of a second - const Audio::Timestamp b(0, 3, 4); - offset = Audio::convertTimeToStreamPos(b, sampleRate, isStereo).totalNumberOfFrames(); - readData = totalFrames - offset; - - TS_ASSERT_EQUALS(s->seek(b), true); - TS_ASSERT_EQUALS(s->endOfData(), false); - TS_ASSERT_EQUALS(s->readBuffer(buffer, readData), readData); - TS_ASSERT_EQUALS(memcmp(buffer, sine + offset, readData * sizeof(int16)), 0); - TS_ASSERT_EQUALS(s->endOfData(), true); - - // Seek to the start of the stream - TS_ASSERT_EQUALS(s->seek(0), true); - TS_ASSERT_EQUALS(s->endOfData(), false); - - // Seek to the mid of the stream - const Audio::Timestamp c(time * 1000 / 2, 1000); - offset = Audio::convertTimeToStreamPos(c, sampleRate, isStereo).totalNumberOfFrames(); - readData = totalFrames - offset; - - TS_ASSERT_EQUALS(s->seek(c), true); - TS_ASSERT_EQUALS(s->endOfData(), false); - TS_ASSERT_EQUALS(s->readBuffer(buffer, readData), readData); - TS_ASSERT_EQUALS(memcmp(buffer, sine + offset, readData * sizeof(int16)), 0); - TS_ASSERT_EQUALS(s->endOfData(), true); - - // Seek to the 1/4th of the last second of the stream - const Audio::Timestamp d(time - 1, 1, 4); - offset = Audio::convertTimeToStreamPos(d, sampleRate, isStereo).totalNumberOfFrames(); - readData = totalFrames - offset; - - TS_ASSERT_EQUALS(s->seek(d), true); - TS_ASSERT_EQUALS(s->endOfData(), false); - TS_ASSERT_EQUALS(s->readBuffer(buffer, readData), readData); - TS_ASSERT_EQUALS(memcmp(buffer, sine + offset, readData * sizeof(int16)), 0); - TS_ASSERT_EQUALS(s->endOfData(), true); - - // Try to seek after the end of the stream - TS_ASSERT_EQUALS(s->seek(Audio::Timestamp(time, 1, 100000)), false); - TS_ASSERT_EQUALS(s->endOfData(), true); - - // Try to seek exactly at the end of the stream - TS_ASSERT_EQUALS(s->seek(Audio::Timestamp(time * 1000, 1000)), true); - TS_ASSERT_EQUALS(s->endOfData(), true); - - delete[] sine; - delete s; - delete[] buffer; - } - -public: - void test_seek_mono() { - seekTest(11025, 2, false, false); - } - - void test_seek_stereo() { - seekTest(11025, 2, true, false); - } - - void test_seek_mono_parted() { - seekTest(11025, 2, false, true); - } - - void test_seek_stereo_parted() { - seekTest(11025, 2, true, true); - } -}; diff --git a/test/sound/timestamp.h b/test/sound/timestamp.h deleted file mode 100644 index d7188f8f44..0000000000 --- a/test/sound/timestamp.h +++ /dev/null @@ -1,241 +0,0 @@ -#include - -#include "sound/timestamp.h" - -class TimestampTestSuite : public CxxTest::TestSuite -{ - public: - void test_diff_add_frames() { - const Audio::Timestamp a(10000, 1000); - const Audio::Timestamp b(10001, 1000); - const Audio::Timestamp c(10002, 1000); - - TS_ASSERT_EQUALS(a.frameDiff(b), -1); - TS_ASSERT_EQUALS(b.frameDiff(a), 1); - TS_ASSERT_EQUALS(c.frameDiff(a), 2); - TS_ASSERT_EQUALS(b.addFrames(2000).frameDiff(a), 2001); - TS_ASSERT_EQUALS(a.frameDiff(b), -1); - TS_ASSERT_EQUALS(b.frameDiff(a), 1); - TS_ASSERT_EQUALS(c.frameDiff(a), 2); - TS_ASSERT_EQUALS(b.addFrames(2000).frameDiff(a.addFrames(-1000)), 3001); - TS_ASSERT_EQUALS(a.frameDiff(b), -1); - TS_ASSERT_EQUALS(b.frameDiff(a), 1); - TS_ASSERT_EQUALS(c.frameDiff(a), 2); - } - - void test_diff_add_msecs() { - Audio::Timestamp ts0(3, 22050); - Audio::Timestamp ts1(0, 22050); - Audio::Timestamp ts2(0, 22050); - - TS_ASSERT_EQUALS(ts0.msecs(), 3); - TS_ASSERT_EQUALS(ts0.totalNumberOfFrames(), 3 * 22050 / 1000); - TS_ASSERT_EQUALS(ts0.numberOfFrames(), 3 * 22050 / 1000); - - ts1 = ts1.addFrames(53248); - TS_ASSERT_EQUALS(ts1.secs(), 2); - TS_ASSERT_EQUALS(ts1.msecs(), 53248 * 1000 / 22050); - TS_ASSERT_EQUALS(ts1.totalNumberOfFrames(), 53248); - TS_ASSERT_EQUALS(ts1.numberOfFrames(), 53248 - 2 * 22050); - ts1 = ts1.addMsecs(47); - TS_ASSERT_EQUALS(ts1.secs(), 2); - TS_ASSERT_EQUALS(ts1.msecs(), 2414+47); - TS_ASSERT_EQUALS(ts1.totalNumberOfFrames(), 47*22050 / 1000 + 53248); - TS_ASSERT_EQUALS(ts1.numberOfFrames(), 47*22050 / 1000 + 53248 - 2 * 22050); - - ts2 = ts2.addMsecs(47); - TS_ASSERT_EQUALS(ts2.secs(), 0); - TS_ASSERT_EQUALS(ts2.msecs(), 47); - TS_ASSERT_EQUALS(ts2.totalNumberOfFrames(), 47*22050 / 1000); - TS_ASSERT_EQUALS(ts2.numberOfFrames(), 47*22050 / 1000); - ts2 = ts2.addFrames(53248); - TS_ASSERT_EQUALS(ts2.secs(), 2); - TS_ASSERT_EQUALS(ts2.msecs(), 2414+47); - TS_ASSERT_EQUALS(ts2.totalNumberOfFrames(), 47*22050 / 1000 + 53248); - TS_ASSERT_EQUALS(ts2.numberOfFrames(), 47*22050 / 1000 + 53248 - 2 * 22050); - } - - void test_ticks() { - const Audio::Timestamp a(1234, 60); - const Audio::Timestamp b(5678, 60); - - TS_ASSERT_EQUALS(a.msecs(), 1234); - TS_ASSERT_EQUALS(b.msecs(), 5678); - - TS_ASSERT_EQUALS(a.secs(), 1); - TS_ASSERT_EQUALS(b.secs(), 5); - - TS_ASSERT_EQUALS(a.msecsDiff(b), 1234 - 5678); - TS_ASSERT_EQUALS(b.msecsDiff(a), 5678 - 1234); - - TS_ASSERT_EQUALS(a.frameDiff(b), (1234 - 5678) * 60 / 1000); - TS_ASSERT_EQUALS(b.frameDiff(a), (5678 - 1234) * 60 / 1000); - - TS_ASSERT_EQUALS(a.addFrames(1).msecs(), (1234 + 1000 * 1/60)); - TS_ASSERT_EQUALS(a.addFrames(59).msecs(), (1234 + 1000 * 59/60)); - TS_ASSERT_EQUALS(a.addFrames(60).msecs(), (1234 + 1000 * 60/60)); - - // As soon as we go back even by only one frame, the msec value - // has to drop by at least one. - TS_ASSERT_EQUALS(a.addFrames(-1).msecs(), (1234 - 1000 * 1/60 - 1)); - TS_ASSERT_EQUALS(a.addFrames(-59).msecs(), (1234 - 1000 * 59/60 - 1)); - TS_ASSERT_EQUALS(a.addFrames(-60).msecs(), (1234 - 1000 * 60/60)); - } - - void test_more_add_diff() { - const Audio::Timestamp c(10002, 1000); - - for (int i = -10000; i < 10000; i++) { - int v = c.addFrames(i).frameDiff(c); - TS_ASSERT_EQUALS(v, i); - } - } - - void test_negate() { - const Audio::Timestamp a = Audio::Timestamp(0, 60).addFrames(13); - const Audio::Timestamp b = -a; - - TS_ASSERT_EQUALS(a.msecs() + 1, -b.msecs()); - TS_ASSERT_EQUALS(a.totalNumberOfFrames(), -b.totalNumberOfFrames()); - TS_ASSERT_EQUALS(a.numberOfFrames(), 60 - b.numberOfFrames()); - } - - void test_add_sub() { - const Audio::Timestamp a = Audio::Timestamp(0, 60).addFrames(13); - const Audio::Timestamp b = -a; - const Audio::Timestamp c = Audio::Timestamp(0, 60).addFrames(20); - - TS_ASSERT_EQUALS((a+a).secs(), 0); - TS_ASSERT_EQUALS((a+a).numberOfFrames(), 2*13); - - TS_ASSERT_EQUALS((a+b).secs(), 0); - TS_ASSERT_EQUALS((a+b).numberOfFrames(), 0); - - TS_ASSERT_EQUALS((a+c).secs(), 0); - TS_ASSERT_EQUALS((a+c).numberOfFrames(), 13+20); - - TS_ASSERT_EQUALS((a-a).secs(), 0); - TS_ASSERT_EQUALS((a-a).numberOfFrames(), 0); - - TS_ASSERT_EQUALS((a-b).secs(), 0); - TS_ASSERT_EQUALS((a-b).numberOfFrames(), 2*13); - - TS_ASSERT_EQUALS((a-c).secs(), -1); - TS_ASSERT_EQUALS((a-c).numberOfFrames(), 60 + (13 - 20)); - } - - void test_diff_with_conversion() { - const Audio::Timestamp a = Audio::Timestamp(10, 1000).addFrames(20); - const Audio::Timestamp b = Audio::Timestamp(10, 1000/5).addFrames(20/5); - const Audio::Timestamp c = Audio::Timestamp(10, 1000*2).addFrames(20*2); - - TS_ASSERT_EQUALS(a.frameDiff(a), 0); - TS_ASSERT_EQUALS(a.frameDiff(b), 0); - TS_ASSERT_EQUALS(a.frameDiff(c), 0); - - TS_ASSERT_EQUALS(b.frameDiff(a), 0); - TS_ASSERT_EQUALS(b.frameDiff(b), 0); - TS_ASSERT_EQUALS(b.frameDiff(c), 0); - - TS_ASSERT_EQUALS(c.frameDiff(a), 0); - TS_ASSERT_EQUALS(c.frameDiff(b), 0); - TS_ASSERT_EQUALS(c.frameDiff(c), 0); - } - - - void test_convert() { - const Audio::Timestamp a = Audio::Timestamp(10, 1000).addFrames(20); - const Audio::Timestamp b = Audio::Timestamp(10, 1000/5).addFrames(20/5); - const Audio::Timestamp c = Audio::Timestamp(10, 1000*2).addFrames(20*2); - - TS_ASSERT_EQUALS(a.convertToFramerate(1000/5), b); - TS_ASSERT_EQUALS(a.convertToFramerate(1000*2), c); - } - - void test_equals() { - const Audio::Timestamp a = Audio::Timestamp(500, 1000); - Audio::Timestamp b = Audio::Timestamp(0, 1000); - Audio::Timestamp c = Audio::Timestamp(0, 100); - - TS_ASSERT_EQUALS(a, Audio::Timestamp(0, 500, 1000)); - - TS_ASSERT(a != b); - TS_ASSERT(a != c); - TS_ASSERT(b == c); - - b = b.addFrames(500); - c = c.addFrames(50); - - TS_ASSERT(a == b); - TS_ASSERT(a == c); - TS_ASSERT(b == c); - } - - - void test_compare() { - const Audio::Timestamp a = Audio::Timestamp(60, 1000); - Audio::Timestamp b = Audio::Timestamp(60, 60); - Audio::Timestamp c = Audio::Timestamp(60, 44100); - - TS_ASSERT(a <= b); - TS_ASSERT(b <= c); - TS_ASSERT(a <= c); - - TS_ASSERT(b >= a); - TS_ASSERT(c >= b); - TS_ASSERT(c >= a); - - b = b.addFrames(60 / 12); - c = c.addFrames(44100 / 10); - - TS_ASSERT(a < b); - TS_ASSERT(b < c); - TS_ASSERT(a < c); - - TS_ASSERT(b > a); - TS_ASSERT(c > b); - TS_ASSERT(c > a); - - TS_ASSERT(a <= b); - TS_ASSERT(b <= c); - TS_ASSERT(a <= c); - - TS_ASSERT(b >= a); - TS_ASSERT(c >= b); - TS_ASSERT(c >= a); - } - - - void test_framerate() { - const Audio::Timestamp a = Audio::Timestamp(500, 1000); - const Audio::Timestamp b = Audio::Timestamp(500, 67); - const Audio::Timestamp c = Audio::Timestamp(500, 100); - const Audio::Timestamp d = Audio::Timestamp(500, 44100); - - TS_ASSERT_EQUALS(a.framerate(), (uint)1000); - TS_ASSERT_EQUALS(b.framerate(), (uint)67); - TS_ASSERT_EQUALS(c.framerate(), (uint)100); - TS_ASSERT_EQUALS(d.framerate(), (uint)44100); - } - - void test_direct_query() { - const Audio::Timestamp a = Audio::Timestamp(0, 22050); - const Audio::Timestamp b = a.addFrames(11025); - const Audio::Timestamp c = Audio::Timestamp(1500, 22050); - - TS_ASSERT_EQUALS(a.secs(), 0); - TS_ASSERT_EQUALS(a.msecs(), 0); - TS_ASSERT_EQUALS(a.numberOfFrames(), 0); - TS_ASSERT_EQUALS(a.totalNumberOfFrames(), 0); - - TS_ASSERT_EQUALS(b.secs(), 0); - TS_ASSERT_EQUALS(b.msecs(), 500); - TS_ASSERT_EQUALS(b.numberOfFrames(), 11025); - TS_ASSERT_EQUALS(b.totalNumberOfFrames(), 11025); - - TS_ASSERT_EQUALS(c.secs(), 1); - TS_ASSERT_EQUALS(c.msecs(), 1500); - TS_ASSERT_EQUALS(c.numberOfFrames(), 11025); - TS_ASSERT_EQUALS(c.totalNumberOfFrames(), 33075); - } -}; diff --git a/video/avi_decoder.cpp b/video/avi_decoder.cpp index 1e2515680f..757d2b740b 100644 --- a/video/avi_decoder.cpp +++ b/video/avi_decoder.cpp @@ -28,14 +28,14 @@ #include "common/stream.h" #include "common/events.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" #include "video/avi_decoder.h" // Audio Codecs -#include "sound/decoders/adpcm.h" -#include "sound/decoders/raw.h" +#include "audio/decoders/adpcm.h" +#include "audio/decoders/raw.h" // Video Codecs #include "video/codecs/cinepak.h" diff --git a/video/avi_decoder.h b/video/avi_decoder.h index 063df1ee72..c2551454db 100644 --- a/video/avi_decoder.h +++ b/video/avi_decoder.h @@ -28,8 +28,8 @@ #include "video/video_decoder.h" #include "video/codecs/codec.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" namespace Video { diff --git a/video/codecs/qdm2.cpp b/video/codecs/qdm2.cpp index eac1954435..8678c94acd 100644 --- a/video/codecs/qdm2.cpp +++ b/video/codecs/qdm2.cpp @@ -30,7 +30,7 @@ #ifdef GRAPHICS_QDM2_H -#include "sound/audiostream.h" +#include "audio/audiostream.h" #include "video/codecs/qdm2data.h" #include "common/array.h" diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp index 3c092d5cf9..c8beb3b604 100644 --- a/video/coktel_decoder.cpp +++ b/video/coktel_decoder.cpp @@ -32,8 +32,8 @@ #ifdef VIDEO_COKTELDECODER_H -#include "sound/audiostream.h" -#include "sound/decoders/raw.h" +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" #include "common/memstream.h" static const uint32 kVideoCodecIndeo3 = MKID_BE('iv32'); diff --git a/video/coktel_decoder.h b/video/coktel_decoder.h index 152cf5941d..9c8263be9d 100644 --- a/video/coktel_decoder.h +++ b/video/coktel_decoder.h @@ -39,7 +39,7 @@ #include "video/video_decoder.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Audio { class QueuingAudioStream; diff --git a/video/qt_decoder.cpp b/video/qt_decoder.cpp index 379dc5d7a2..c855360e2b 100644 --- a/video/qt_decoder.cpp +++ b/video/qt_decoder.cpp @@ -41,8 +41,8 @@ #include "common/zlib.h" // Audio codecs -#include "sound/decoders/adpcm.h" -#include "sound/decoders/raw.h" +#include "audio/decoders/adpcm.h" +#include "audio/decoders/raw.h" #include "video/codecs/qdm2.h" // Video codecs diff --git a/video/qt_decoder.h b/video/qt_decoder.h index 47da683f39..809c8a718d 100644 --- a/video/qt_decoder.h +++ b/video/qt_decoder.h @@ -41,8 +41,8 @@ #include "video/video_decoder.h" #include "video/codecs/codec.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" namespace Common { class File; diff --git a/video/smk_decoder.cpp b/video/smk_decoder.cpp index d7412e99c3..a9f2eeeb5b 100644 --- a/video/smk_decoder.cpp +++ b/video/smk_decoder.cpp @@ -35,9 +35,9 @@ #include "common/stream.h" #include "common/system.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" namespace Video { diff --git a/video/smk_decoder.h b/video/smk_decoder.h index 146d8a7935..2d01909a44 100644 --- a/video/smk_decoder.h +++ b/video/smk_decoder.h @@ -27,7 +27,7 @@ #define VIDEO_SMK_PLAYER_H #include "video/video_decoder.h" -#include "sound/mixer.h" +#include "audio/mixer.h" namespace Audio { class QueuingAudioStream; diff --git a/video/video_decoder.h b/video/video_decoder.h index 5b6c882d09..97cd133bc0 100644 --- a/video/video_decoder.h +++ b/video/video_decoder.h @@ -33,7 +33,7 @@ #include "graphics/surface.h" #include "graphics/pixelformat.h" -#include "sound/timestamp.h" // TODO: Move this to common/ ? +#include "audio/timestamp.h" // TODO: Move this to common/ ? namespace Common { -- cgit v1.2.3