Merge branch 'master' into mm

Conflicts:
	engines/access/access.cpp
	engines/access/asurface.h
	engines/access/bubble_box.cpp
	engines/access/bubble_box.h
	engines/access/martian/martian_game.cpp
	engines/access/player.cpp
	engines/access/player.h
	engines/access/resources.cpp
	engines/access/screen.cpp
	engines/access/screen.h
	engines/access/sound.cpp
	engines/access/sound.h
This commit is contained in:
Willem Jan Palenstijn 2015-07-22 22:37:40 +02:00
commit 6ec9c81b57
885 changed files with 76342 additions and 29223 deletions

15
AUTHORS
View file

@ -259,8 +259,10 @@ ScummVM Team
Einar Johan T. Somaaen
Tobia Tesan
ZVision:
Z-Vision:
Adrian Astley
Filippos Karapetis
Anton Yarcev
Backend Teams
-------------
@ -483,7 +485,7 @@ Other contributions
German:
Simon Sawatzki
Lothar Serra Mari - (retired)
Lothar Serra Mari
Hungarian:
Alex Bevilacqua
@ -524,6 +526,12 @@ Other contributions
Victor Gonzalez - Soltys Spanish translation
Alejandro Gomez de la Munoza - Soltys Spanish translation
CGE2:
Arnaud Boutonne - Sfinx English translation
Thierry Crozat - Sfinx English translation
Peter Bozso - Sfinx English translation editor
Ryan Clark - Sfinx English translation editor
Drascula:
Thierry Crozat - Improve French translation
@ -557,6 +565,7 @@ Other contributions
Jeroen Janssen - Numerous readability and bugfix patches
Keith Kaisershot - Several Pegasus Prime patches
Andreas Karlsson - Initial port for SymbianOS
Stefan Kristiansson - Initial work on SDL2 support
Claudio Matsuoka - Daily Linux builds
Thomas Mayer - PSP port contributions
Sean Murray - ScummVM tools GUI application (GSoC 2007
@ -661,7 +670,7 @@ Special thanks to
Jimmi Thogersen - For ScummRev, and much obscure code/documentation
Tristan - For additional work on the original MT-32 emulator
James Woodcock - Soundtrack enhancements
Anton Yartsev - For the original re-implementation of the ZVision
Anton Yartsev - For the original re-implementation of the Z-Vision
engine
Tony Warriner and everyone at Revolution Software Ltd. for sharing with us

View file

@ -1,5 +1,5 @@
ScummVM
Copyright (C) 2001-2014 by the following:
Copyright (C) 2001-2015 by the following:
If you have contributed to this project then you deserve to be on this
list. Contact us (see: AUTHORS) and we'll add you.

View file

@ -91,6 +91,7 @@ ifeq "$(findstring config.mk,$(MAKEFILE_LIST))" "config.mk"
LDFLAGS="$(SAVED_LDFLAGS)" CXX="$(SAVED_CXX)" \
CXXFLAGS="$(SAVED_CXXFLAGS)" CPPFLAGS="$(SAVED_CPPFLAGS)" \
ASFLAGS="$(SAVED_ASFLAGS)" WINDRESFLAGS="$(SAVED_WINDRESFLAGS)" \
SDL_CONFIG="$(SAVED_SDL_CONFIG)" \
$(srcdir)/configure $(SAVED_CONFIGFLAGS)
else
$(error You need to run $(srcdir)/configure before you can run make. Check $(srcdir)/configure --help for a list of parameters)

44
NEWS
View file

@ -3,15 +3,57 @@ For a more comprehensive changelog of the latest experimental code, see:
1.8.0 (????-??-??)
New Games:
- Added support for Rex Nebular and the Cosmic Gender Bender.
- Added support for Sfinx.
- Added support for Zork Nemesis: The Forbidden Lands.
- Added support for Zork: Grand Inquisitor.
General:
- Updated Munt MT-32 emulation code to version 1.5.0.
3 Skulls of the Toltecs:
- Improved AdLib music support.
AGI:
- It is now possible to disable mouse support (except for Amiga versions
and fanmade games, that require a mouse).
- Fix incorrect volume attenuation in PCjr sound code (bug #6858).
AGOS:
- Fixed arpeggio effect used in music of Amiga version of Elvira 1.
- Fixed loading and saving progress in the PC version of Waxworks.
- Fixed verb area been removed in Amiga versions of Simon the Sorcerer 1.
- Added Accolade AdLib & MT32 music drivers for the games:
Elvira 1, Elvira 2, Waxworks and Simon the Sorcerer 1 demo.
Broken Sword 1:
- Fix speech endianness detection on big endian systems for the mac
- Fix speech endianness detection on big endian systems for the Macintosh
version (bug #6720).
- Fix crash when reloading a game from the Main Menu while in the bull's
head scene (bug #6728). It may have been happening in other scenes as
well.
MADE:
- Improved AdLib music support in Return to Zork.
SAGA:
- Improved AdLib music support.
SCI:
- Handling of music priority has been greatly improved.
- A lot of fixes for original game script bugs that also occurred when
using the original interpreter.
KQ6 (Dual Mode), LSL5, QfG1 (EGA), QfG1 (VGA), QfG2, QfG3, SQ1, SQ4 (CD)
- Restoring from the ScummVM in-game menu should now work all the time.
- Improve support for Japanese PC-9801 games.
SCUMM:
- It is now possible to play Maniac Mansion from within Day of the
Tentacle, with a few caveats. See README for details.
Tinsel:
- Improved AdLib music support in Discworld 1.
1.7.0 (2014-07-21)
New Games:
- Added support for Chivalry is Not Dead.

300
README
View file

@ -15,26 +15,27 @@ Table of Contents:
* 2.1 Reporting Bugs
3.0) Supported Games
* 3.1 Copy Protection
* 3.2 Commodore64 games notes
* 3.3 Maniac Mansion NES notes
* 3.4 Macintosh games notes
* 3.5 Multi-CD games notes
* 3.6 The Curse of Monkey Island notes
* 3.7 Broken Sword games notes
* 3.8 Beneath a Steel Sky notes
* 3.9 Flight of the Amazon Queen notes
* 3.10 Gobliiins notes
* 3.11 Inherit the Earth: Quest for the Orb notes
* 3.12 Simon the Sorcerer notes
* 3.13 The Feeble Files notes
* 3.14 The Legend of Kyrandia notes
* 3.15 Sierra AGI games Predictive Input Dialog notes
* 3.16 Mickey's Space Adventure notes
* 3.17 Winnie the Pooh notes
* 3.18 Troll's Tale notes
* 3.19 Dragon History notes
* 3.20 Simultaneous speech and subtitles in Sierra SCI games
* 3.21 Known Problems
* 3.2 Day of the Tentacle notes
* 3.3 Commodore64 games notes
* 3.4 Maniac Mansion NES notes
* 3.5 Macintosh games notes
* 3.6 Multi-CD games notes
* 3.7 The Curse of Monkey Island notes
* 3.8 Broken Sword games notes
* 3.9 Beneath a Steel Sky notes
* 3.10 Flight of the Amazon Queen notes
* 3.11 Gobliiins notes
* 3.12 Inherit the Earth: Quest for the Orb notes
* 3.13 Simon the Sorcerer notes
* 3.14 The Feeble Files notes
* 3.15 The Legend of Kyrandia notes
* 3.16 Sierra AGI games Predictive Input Dialog notes
* 3.17 Mickey's Space Adventure notes
* 3.18 Winnie the Pooh notes
* 3.19 Troll's Tale notes
* 3.20 Dragon History notes
* 3.21 Simultaneous speech and subtitles in Sierra SCI games
* 3.22 Known Problems
4.0) Supported Platforms
5.0) Running ScummVM
* 5.1 Command Line Options
@ -200,7 +201,7 @@ SCUMM Games by LucasArts:
The Dig [dig]
The Curse of Monkey Island [comi]
AGI Games by Sierra:
AGI and preAGI Games by Sierra:
The Black Cauldron [bc]
Gold Rush! [goldrush]
King's Quest I [kq1]
@ -217,6 +218,9 @@ AGI Games by Sierra:
Space Quest I: The Sarien Encounter [sq1]
Space Quest II: Vohaul's Revenge [sq2]
Fanmade Games [agi-fanmade]
Mickey's Space Adventure [mickey]
Troll's Tale [troll]
Winnie the Pooh in the Hundred Acre Wood [winnie]
AGOS Games by Adventuresoft/Horrorsoft:
Elvira - Mistress of the Dark [elvira1]
@ -235,6 +239,13 @@ AGOS Games by Adventuresoft/Horrorsoft:
- Swampy Adventures [swampy]
The Feeble Files [feeble]
Composer Games by Animation Magic:
Darby the Dragon [darby]
Gregory and the Hot Air Balloon [gregory]
Magic Tales: Liam Finds a Story [liam]
The Princess and the Crab [princess]
Sleeping Cub's Test of Courage [sleepingcub]
GOB Games by Coktel Vision:
Bambou le sauveur de la jungle [bambou]
Bargon Attack [bargon]
@ -250,6 +261,22 @@ GOB Games by Coktel Vision:
Urban Runner [urban]
Ween: The Prophecy [ween]
Living Books Games:
Aesop's Fables: The Tortoise and the Hare [tortoise]
Arthur's Birthday [arthurbday]
Arthur's Teacher Trouble [arthur]
Dr. Seuss's ABC [seussabc]
Green Eggs and Ham [greeneggs]
Harry and the Haunted House [harryhh]
Just Grandma and Me [grandma]
Little Monster at School [lilmonster]
Ruff's Bone [ruff]
Sheila Rae, the Brave [sheila]
Stellaluna [stellaluna]
The Berenstain Bears Get in a Fight [bearfight]
The Berenstain Bears in the Dark [beardark]
The New Kid on the Block [newkid]
MADE Games by Activision:
Leather Goddesses of Phobos 2 [lgop2]
Return to Zork [rtz]
@ -257,34 +284,106 @@ MADE Games by Activision:
The Manhole [manhole]
Other Games:
3 Skulls of the Toltecs [toltecs]
Blue Force [blueforce]
Beneath a Steel Sky [sky]
Broken Sword: The Shadow of the Templars [sword1]
Broken Sword II: The Smoking Mirror [sword2]
Bud Tucker in Double Trouble [tucker]
Cruise for a Corpse [cruise]
Discworld [dw]
Discworld 2: Missing Presumed ...!? [dw2]
Dragon History [draci]
Drascula: The Vampire Strikes Back [drascula]
DreamWeb [dreamweb]
Eye of the Beholder [eob]
Eye of the Beholder II: The Legend of
Darkmoon [eob2]
Flight of the Amazon Queen [queen]
Future Wars [fw]
Hopkins FBI [hopkins]
Hugo's House of Horrors [hugo1]
Hugo 2: Whodunit? [hugo2]
Hugo 3: Jungle of Doom [hugo3]
I Have No Mouth, and I Must Scream [ihnm]
Inherit the Earth: Quest for the Orb [ite]
Nippon Safes Inc. [nippon]
Lands of Lore: The Throne of Chaos [lol]
Lure of the Temptress [lure]
Mortville Manor [mortevielle]
Nippon Safes Inc. [nippon]
Ringworld: Revenge Of The Patriarch [ringworld]
Return to Ringworld [ringworld2]
Sfinx [sfinx]
Soltys [soltys]
TeenAgent [teenagent]
The Journeyman Project: Pegasus Prime [pegasus]
The Legend of Kyrandia [kyra1]
The Legend of Kyrandia: The Hand of Fate [kyra2]
The Legend of Kyrandia: Malcolm's Revenge [kyra3]
The 7th Guest [t7g]
The Neverhood [neverhood]
Tony Tough and the Night of Roasted Moths [tony]
Toonstruck [toon]
Touche: The Adventures of the Fifth
Musketeer [touche]
Voyeur [voyeur]
SCI Games by Sierra Entertainment:
Castle of Dr. Brain [castlebrain]
Codename: ICEMAN [iceman]
Conquests of Camelot [camelot]
Conquests of the Longbow [longbow]
EcoQuest: The Search for Cetus [ecoquest]
EcoQuest 2: Lost Secret of the Rainforest [ecoquest2]
Freddy Pharkas: Frontier Pharmacist [freddypharkas]
Hoyle's Book of Games 1 [hoyle1]
Hoyle's Book of Games 2 [hoyle2]
Hoyle's Book of Games 3 [hoyle3]
Hoyle Classic Card Games [hoyle4]
Jones in the Fast Lane [jones]
King's Quest I [kq1sci]
King's Quest IV [kq4sci]
King's Quest V [kq5]
King's Quest VI [kq6]
Laura Bow: The Colonel's Bequest [laurabow]
Laura Bow 2: The Dagger of Amon Ra [laurabow2]
Leisure Suit Larry 1 [lsl1sci]
Leisure Suit Larry 2 [lsl2]
Leisure Suit Larry 3 [lsl3]
Leisure Suit Larry 5 [lsl5]
Leisure Suit Larry 6 [lsl6]
Mixed-up Fairy Tales [fairytales]
Mixed-up Mother Goose [mothergoose]
Pepper's Adventures in Time [pepper]
Police Quest 1 [pq1sci]
Police Quest 2 [pq2]
Police Quest 3 [pq3]
Quest for Glory 1/Hero's Quest [qfg1]
Quest for Glory 1 [qfg1vga]
Quest for Glory 2 [qfg2]
Quest for Glory 3 [qfg3]
Slater & Charlie Go Camping [slater]
Space Quest I [sq1sci]
Space Quest III [sq3]
Space Quest IV [sq4]
Space Quest V [sq5]
The Island of Dr. Brain [islandbrain]
Wintermute Games:
Chivalry is Not Dead [chivalry]
Z-Vision Games by Activision:
Zork Nemesis: The Forbidden Lands [znemesis]
Zork: Grand Inquisitor [zgi]
SCUMM Games by Humongous Entertainment:
Backyard Baseball [baseball]
Backyard Baseball 2001 [baseball2001]
Backyard Baseball 2003 [baseball2003]
Backyard Football [football]
Backyard Football 2002 [football2002]
Bear Stormin' [brstorm]
Big Thinkers First Grade [thinker1]
Big Thinkers Kindergarten [thinkerk]
Blue's 123 Time Activities [Blues123Time]
@ -309,6 +408,7 @@ SCUMM Games by Humongous Entertainment:
Let's Explore the Airport with Buzzy [airport]
Let's Explore the Farm with Buzzy [farm]
Let's Explore the Jungle with Buzzy [jungle]
Pajama Sam: Games to Play on Any Day [pjgames]
Pajama Sam 1: No Need to Hide When It's
Dark Outside [pajama]
Pajama Sam 2: Thunder and Lightning
@ -333,38 +433,20 @@ SCUMM Games by Humongous Entertainment:
SPY Fox in Cheese Chase [chase]
SPY Fox in Hold the Mustard [mustard]
Living Books Games:
Aesop's Fables: The Tortoise and the Hare [tortoise]
Arthur's Birthday [arthurbday]
Arthur's Teacher Trouble [arthur]
Dr. Seuss's ABC [seussabc]
Green Eggs and Ham [greeneggs]
Harry and the Haunted House [harryhh]
Just Grandma and Me [grandma]
Little Monster at School [lilmonster]
Ruff's Bone [ruff]
Sheila Rae, the Brave [sheila]
Stellaluna [stellaluna]
The Berenstain Bears Get in a Fight [bearfight]
The Berenstain Bears in the Dark [beardark]
The New Kid on the Block [newkid]
The following games should load, but are not yet fully playable. Play
these at your own risk, and please do not file bug reports about them.
If you want the latest updates on game compatibility, visit our web site
and view the compatibility chart.
Backyard Football 2002 [football2002]
Backyard Soccer [soccer]
Backyard Soccer MLS [soccermls]
Backyard Soccer 2004 [soccer2004]
Blue's Treasure Hunt [BluesTreasureHunt]
Pajama Sam: Games to Play on Any Day [pjgames]
The following games are based on the SCUMM engine, but NOT supported
by ScummVM (yet):
Other Humongous Entertainment games
Moonbase Commander
Please be aware that the engines may contain bugs and unimplemented
features that sometimes make it impossible to finish the game. Save
@ -410,7 +492,29 @@ ScummVM will skip copy protection in the following games:
* Zak McKracken and the Alien Mindbenders
3.2) Commodore64 games notes:
3.2) Day of the Tentacle notes:
---- --------------------------
At one point in the game, you come across a computer that allows you
to play the original Maniac Mansion as an easter egg. ScummVM supports
this, with a few caveats:
ScummVM will scan your configuration file for a game that's in a
'Maniac' sub-folder of your Day of the Tentacle folder. If you've
copied the data files from the CD version, this should already be the
case but you have to add the game to ScummVM as well.
To return to Day of the Tentacle, press F5 and select "Return to
Launcher".
This means that you could in theory use any game as the easter egg.
Indeed, there is a "secret" configuration setting, "easter_egg", to
override the ID of the game to run. Be aware, though, that not all
games support returning to the launcher, and setting it up to use Day
of the Tentacle itself as the easter egg game is not recommended.
3.3) Commodore64 games notes:
---- ------------------------
Both Maniac Mansion and Zak McKracken run but Maniac Mansion is not yet
playable. Simply name the D64 disks "maniac1.d64" and "maniac2.d64"
@ -424,7 +528,7 @@ to Commodore64. We recommend using the much simpler approach described
in the previous paragraph.
3.3) Maniac Mansion NES notes:
3.4) Maniac Mansion NES notes:
---- -------------------------
Supported versions are English GB (E), French (F), German (G), Italian (I),
Swedish (SW) and English US (U). ScummVM requires just the PRG section
@ -453,7 +557,7 @@ section. To do so use the 'extract_mm_nes' utility from the tools
package.
3.4) Macintosh games notes:
3.5) Macintosh games notes:
---- ----------------------
All LucasArts SCUMM based adventures, except COMI, also exist in versions
for the Macintosh. ScummVM can use most (all?) of them, however, in some
@ -481,7 +585,7 @@ disk see:
http://wiki.scummvm.org/index.php/HOWTO-Mac_Games
3.5) Multi-CD games notes:
3.6) Multi-CD games notes:
---- ---------------------
In general, ScummVM does not deal very well with Multi-CD games. This is
because ScummVM assumes everything about a game can be found in one
@ -496,7 +600,7 @@ files. Usually, when a file appears on more than one CD you can pick
either of them.
3.6) The Curse of Monkey Island notes:
3.7) The Curse of Monkey Island notes:
---- ---------------------------------
For this game, you will need the comi.la0, comi.la1 and comi.la2 files.
The comi.la0 file can be found on either CD, but since they are
@ -508,7 +612,7 @@ two CDs. Some of the files appear on both CDs, but again they're
identical.
3.7) Broken Sword games notes:
3.8) Broken Sword games notes:
---- -------------------------
The instructions for the Broken Sword games are for the Sold-Out
Software versions, with each game on two CDs, since these were the
@ -517,7 +621,7 @@ them. Hopefully they are general enough to be useful to other releases
as well.
3.7.1) Broken Sword games cutscenes:
3.8.1) Broken Sword games cutscenes:
------ -----------------------------
The cutscenes for the Broken Sword games have a bit of a history (see
the next section, if you are interested), but in general all you need to
@ -558,7 +662,7 @@ currently does not work when running PlayStation videos. (Broken Sword
II already has subtitles; no extra work is needed for them.)
3.7.2) Broken Sword games cutscenes, in retrospect:
3.8.2) Broken Sword games cutscenes, in retrospect:
------ --------------------------------------------
The original releases of the Broken Sword games used RAD Game Tools's
Smacker(tm) format. As RAD was unwilling to open the older legacy
@ -583,7 +687,7 @@ decoding MPEG movies added a lot of complexity, and they didn't look as
good as the Smacker and DXA versions anyway.
3.7.3) Broken Sword:
3.8.3) Broken Sword:
------ -------------
For this game, you will need all of the files from the clusters
directories on both CDs. For the Windows and Macintosh versions, you
@ -600,7 +704,7 @@ makes little difference. The PlayStation version requires tunes.dat and
tunes.tab.
3.7.4) Broken Sword II:
3.8.4) Broken Sword II:
------ ----------------
For this game, you will need all of the files from the clusters
directories on both CDs. (Actually, a few of them may not be strictly
@ -615,7 +719,7 @@ In addition, you will need the cd.inf and, optionally, the startup.inf
files from the sword2 directory on CD 1.
3.8) Beneath a Steel Sky notes:
3.9) Beneath a Steel Sky notes:
---- --------------------------
Starting with ScummVM 0.8.0 you need the additional 'SKY.CPT' file to
run Beneath a Steel Sky.
@ -626,7 +730,7 @@ files (SKY.DNR, SKY.DSK), in your extrapath, or in the directory where
your ScummVM executable resides.
3.9) Flight of the Amazon Queen notes:
3.10) Flight of the Amazon Queen notes:
---- ---------------------------------
In order to use a non-freeware version of Flight of the Amazon Queen
(from original CD), you will need to place the 'queen.tbl' file
@ -641,7 +745,7 @@ specific version, and thus removing the run-time dependency on the
sound effects with MP3, OGG or FLAC.
3.10) Gobliiins notes:
3.11) Gobliiins notes:
----- ----------------
The CD versions of the Gobliiins series contain one big audio track
which you need to rip (see the section on using compressed audio files)
@ -651,7 +755,7 @@ track and its volume is therefore changed with the music volume control
as well.
3.11) Inherit the Earth: Quest for the Orb notes:
3.12) Inherit the Earth: Quest for the Orb notes:
----- -------------------------------------------
In order to run the Mac OS X Wyrmkeep re-release of the game you will
need to copy over data from the CD to your hard disk. If you're on a PC
@ -671,14 +775,14 @@ format, as they should include both resource and data forks. Copy all
'ITE *' files.
3.12) Simon the Sorcerer 1 and 2 notes:
3.13) Simon the Sorcerer 1 and 2 notes:
----- ---------------------------------
If you have the dual version of Simon the Sorcerer 1 or 2 on CD, you
will find the Windows version in the main directory of the CD and the
DOS version in the DOS directory of the CD.
3.13) The Feeble Files notes:
3.14) The Feeble Files notes:
----- -----------------------
If you have the Windows version of The Feeble Files, there are several
things to note.
@ -696,7 +800,7 @@ Rename voices.wav on CD3 to voices3.wav
Rename voices.wav on CD4 to voices4.wav
3.14) The Legend of Kyrandia notes:
3.15) The Legend of Kyrandia notes:
----- -----------------------------
To run The Legend of Kyrandia under ScummVM you need the 'kyra.dat'
file. The file should always be included in official ScummVM packages.
@ -707,7 +811,7 @@ thus you only need to grab it in case ScummVM complains about the file
being missing.
3.15) Sierra AGI games Predictive Input Dialog notes:
3.16) Sierra AGI games Predictive Input Dialog notes:
----- -----------------------------------------------
The Predictive Input Dialog is a ScummVM aid for running AGI engine
games (which notoriously require command line input) on devices with
@ -761,7 +865,7 @@ naturally mapping the functionality to the numeric keypad. Also, the
dialog's buttons can be navigated with the arrow and the enter keys.
3.16) Mickey's Space Adventure notes:
3.17) Mickey's Space Adventure notes:
----- -------------------------------
To run Mickey's Space Adventure under ScummVM, the original executable
of the game (mickey.exe) is needed together with the game's data files.
@ -776,7 +880,7 @@ game's screen to change location, similar to many adventure games, which
is simpler and more straightforward than moving around using the menu.
3.17) Winnie the Pooh notes:
3.18) Winnie the Pooh notes:
----- ----------------------
It is possible to import saved games from the original interpreter of the
game into ScummVM.
@ -791,14 +895,14 @@ game's screen to change location, similar to many adventure games, which
is simpler and more straightforward than moving around using the menu.
3.18) Troll's Tale notes:
3.19) Troll's Tale notes:
----- -------------------
The original game came in a PC booter disk, therefore it is necessary to
dump the contents of that disk in an image file and name it "troll.img"
to be able to play the game under ScummVM.
3.19) Dragon History notes:
3.20) Dragon History notes:
----- ---------------------
There are 4 language variants of the game: Czech, English, Polish and
German. Each of them is distributed in a separate archive. The only
@ -816,7 +920,7 @@ All game files and the walkthrough can be downloaded from
http://www.ucw.cz/draci-historie/index-en.html
3.20) Simultaneous speech and subtitles in Sierra SCI games:
3.21) Simultaneous speech and subtitles in Sierra SCI games:
----- ------------------------------------------------------
Certain CD versions of Sierra SCI games had both speech and text
resources. Some have an option to toggle between the two, but there are
@ -864,7 +968,7 @@ Space Quest 4 CD:
options dialog, or via ScummVM's audio options.
3.21) Known Problems:
3.22) Known Problems:
----- ---------------
This release has the following known problems. There is no need to
report them, although patches to fix them are welcome. If you discover a
@ -909,7 +1013,11 @@ site, please see the section on reporting bugs.
Inherit the Earth: Quest for the Orb
- Amiga versions aren't supported
Simon the Sorcerer 1:
Lure of the Temptress
- No Roland MT-32 support
- Sound support is incomplete and doesn't sound like original
Simon the Sorcerer 1:
- Subtitles aren't available in the English and German CD versions
as they are missing the majority of subtitles.
@ -1244,6 +1352,7 @@ Engines which currently support returning to the Launcher are:
TOUCHE
TSAGE
TUCKER
ZVISION
5.5) Hotkeys:
@ -1408,6 +1517,26 @@ other games.
t - Switch between 'Voice only',
'Voice and Text' and 'Text only'
Zork: Grand Inquisitor
Ctrl-s - Save
Ctrl-r - Restore
Ctrl-q - Quit
Ctrl-p - Preferences
F1 - Help
F5 - Inventory
F6 - Spellbook
F7 - Score
F8 - Put away current object/forget spell
F9 - Extract coin (must have the coin bag)
Space - Skips movies
Zork Nemesis: The Forbidden Lands
Ctrl-s - Save
Ctrl-r - Restore
Ctrl-q - Quit
Ctrl-p - Preferences
Space - Skips movies
Note that using Ctrl-f or Ctrl-g is not recommended: games can crash
when being run faster than their normal speed, as scripts will lose
synchronisation.
@ -1527,6 +1656,7 @@ Where 'xxx' is exact the saved game slot (ie 001) under ScummVM
TOUCHE
TSAGE
TUCKER
ZVISION
--save-slot/-x:
@ -1559,6 +1689,7 @@ Where 'xxx' is exact the saved game slot (ie 001) under ScummVM
TOUCHE
TSAGE
TUCKER
ZVISION
7.0) Music and Sound:
@ -2137,6 +2268,10 @@ Sierra games using the AGI engine add the following non-standard keywords:
originalsaveload bool If true, the original save/load screens are
used instead of the enhanced ScummVM ones
altamigapalette bool Use an alternative palette, common for all
Amiga games. This was the old behavior
mousesupport bool Enables mouse support. Allows to use mouse
for movement and in game menus
Sierra games using the SCI engine add the following non-standard keywords:
@ -2148,6 +2283,12 @@ Sierra games using the SCI engine add the following non-standard keywords:
native_fb01 bool If true, the music driver for an IBM Music
Feature card or a Yamaha FB-01 FM synth module
is used for MIDI output
use_cdaudio bool Use CD audio instead of in-game audio,
when available
windows_cursors bool Use the Windows cursors (smaller and monochrome)
instead of the DOS ones (King's Quest 6)
silver_cursors bool Use the alternate set of silver cursors,
instead of the normal golden ones (Space Quest 4)
Broken Sword II adds the following non-standard keywords:
@ -2240,6 +2381,27 @@ The 7th Guest adds the following non-standard keyword:
normal speed, to avoid music synchronization
issues
Zork Nemesis: The Forbidden Lands adds the following non-standard keywords:
originalsaveload bool If true, the original save/load screens are
used instead of the enhanced ScummVM ones
doublefps bool If true, game FPS are increased from 30 to 60
venusenabled bool If true, the in-game Venus help system is
enabled
noanimwhileturning bool If true, animations are disabled while turning
in panoramic mode
Zork: Grand Inquisitor adds the following non-standard keywords:
originalsaveload bool If true, the original save/load screens are
used instead of the enhanced ScummVM ones
doublefps bool If true, game FPS are increased from 30 to 60
noanimwhileturning bool If true, animations are disabled while turning
in panoramic mode
mpegmovies bool If true, the hires MPEG movies are used in the
DVD version of the game, instead of the lowres
AVI ones
8.2) Custom game options that can be toggled via the GUI
---- ---------------------------------------------------
@ -2308,9 +2470,11 @@ debug messages (see http://www.sysinternals.com/ntw2k/freeware/debugview.shtml).
* Type "./configure" in the ScummVM directory.
* You can now type 'make' to create a command line binary.
* To get a version you can run from Finder, type 'make bundle' which
will create ScummVM.app (this only works out of the box if you
installed SDL into /sw (as happens if you are using Fink). If you
have installed SDL in another way, you will have to edit ports.mk).
will create ScummVM.app (this tries to detect where the static libraries
are installed, which should work in most cases, but if it doesn't you
will need to specify this path with --with-staticlib-prefix= when calling
configure - for example "./configure --with-staticlib-prefix=/Users/foo"
if the libraries are in /Users/foo/lib).
* For more information refer to:
http://wiki.scummvm.org/index.php/Compiling_ScummVM/MacOS_X_Crosscompiling

View file

@ -927,18 +927,20 @@ static void createLookupTable() {
//
////////////////////////////////////////
class MidiDriver_ADLIB : public MidiDriver_Emulated {
class MidiDriver_ADLIB : public MidiDriver {
friend class AdLibPart;
friend class AdLibPercussionChannel;
public:
MidiDriver_ADLIB(Audio::Mixer *mixer);
MidiDriver_ADLIB();
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);
bool isOpen() const { return _isOpen; }
uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
void setPitchBendRange(byte channel, uint range);
void sysEx_customInstrument(byte channel, uint32 type, const byte *instr);
@ -946,10 +948,7 @@ public:
MidiChannel *allocateChannel();
MidiChannel *getPercussionChannel() { return &_percussion; } // Percussion partially supported
// AudioStream API
bool isStereo() const { return _opl->isStereo(); }
int getRate() const { return _mixer->getOutputRate(); }
virtual void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc);
private:
bool _scummSmallHeader; // FIXME: This flag controls a special mode for SCUMM V3 games
@ -963,6 +962,9 @@ private:
byte *_regCacheSecondary;
#endif
Common::TimerManager::TimerProc _adlibTimerProc;
void *_adlibTimerParam;
int _timerCounter;
uint16 _channelTable2[9];
@ -974,7 +976,8 @@ private:
AdLibPart _parts[32];
AdLibPercussionChannel _percussion;
void generateSamples(int16 *buf, int len);
bool _isOpen;
void onTimer();
void partKeyOn(AdLibPart *part, const AdLibInstrument *instr, byte note, byte velocity, const AdLibInstrument *second, byte pan);
void partKeyOff(AdLibPart *part, byte note);
@ -1376,8 +1379,7 @@ void AdLibPercussionChannel::sysEx_customInstrument(uint32 type, const byte *ins
// MidiDriver method implementations
MidiDriver_ADLIB::MidiDriver_ADLIB(Audio::Mixer *mixer)
: MidiDriver_Emulated(mixer) {
MidiDriver_ADLIB::MidiDriver_ADLIB() {
uint i;
_scummSmallHeader = false;
@ -1403,13 +1405,16 @@ MidiDriver_ADLIB::MidiDriver_ADLIB(Audio::Mixer *mixer)
_timerIncrease = 0xD69;
_timerThreshold = 0x411B;
_opl = 0;
_adlibTimerProc = 0;
_adlibTimerParam = 0;
_isOpen = false;
}
int MidiDriver_ADLIB::open() {
if (_isOpen)
return MERR_ALREADY_OPEN;
MidiDriver_Emulated::open();
_isOpen = true;
int i;
AdLibVoice *voice;
@ -1434,7 +1439,7 @@ int MidiDriver_ADLIB::open() {
_opl3Mode = false;
}
#endif
_opl->init(getRate());
_opl->init();
_regCache = (byte *)calloc(256, 1);
@ -1452,8 +1457,7 @@ int MidiDriver_ADLIB::open() {
}
#endif
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
_opl->start(new Common::Functor0Mem<void, MidiDriver_ADLIB>(this, &MidiDriver_ADLIB::onTimer));
return 0;
}
@ -1462,7 +1466,8 @@ void MidiDriver_ADLIB::close() {
return;
_isOpen = false;
_mixer->stopHandle(_mixerSoundHandle);
// Stop the OPL timer
_opl->stop();
uint i;
for (i = 0; i < ARRAYSIZE(_voices); ++i) {
@ -1616,14 +1621,10 @@ void MidiDriver_ADLIB::adlibWriteSecondary(byte reg, byte value) {
}
#endif
void MidiDriver_ADLIB::generateSamples(int16 *data, int len) {
if (_opl->isStereo()) {
len *= 2;
}
_opl->readBuffer(data, len);
}
void MidiDriver_ADLIB::onTimer() {
if (_adlibTimerProc)
(*_adlibTimerProc)(_adlibTimerParam);
_timerCounter += _timerIncrease;
while (_timerCounter >= _timerThreshold) {
_timerCounter -= _timerThreshold;
@ -1655,6 +1656,11 @@ void MidiDriver_ADLIB::onTimer() {
}
}
void MidiDriver_ADLIB::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
_adlibTimerProc = timerProc;
_adlibTimerParam = timerParam;
}
void MidiDriver_ADLIB::mcOff(AdLibVoice *voice) {
AdLibVoice *tmp;
@ -2300,7 +2306,7 @@ MusicDevices AdLibEmuMusicPlugin::getDevices() const {
}
Common::Error AdLibEmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
*mididriver = new MidiDriver_ADLIB(g_system->getMixer());
*mididriver = new MidiDriver_ADLIB();
return Common::kNoError;
}

349
audio/alsa_opl.cpp Normal file
View file

@ -0,0 +1,349 @@
/* 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.
*
*/
/* OPL implementation for hardware OPL using ALSA Direct FM API.
*
* Caveats and limitations:
* - Pretends to be a softsynth (emitting silence).
* - Dual OPL2 mode requires OPL3 hardware.
* - Every register write leads to a series of register writes on the hardware,
* due to the lack of direct register access in the ALSA Direct FM API.
* - No timers
*/
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "common/scummsys.h"
#include "common/debug.h"
#include "common/str.h"
#include "audio/fmopl.h"
#include <sys/ioctl.h>
#include <alsa/asoundlib.h>
#include <sound/asound_fm.h>
namespace OPL {
namespace ALSA {
class OPL : public ::OPL::RealOPL {
private:
enum {
kOpl2Voices = 9,
kVoices = 18,
kOpl2Operators = 18,
kOperators = 36
};
Config::OplType _type;
int _iface;
snd_hwdep_t *_opl;
snd_dm_fm_voice _oper[kOperators];
snd_dm_fm_note _voice[kVoices];
snd_dm_fm_params _params;
int index[2];
static const int voiceToOper0[kVoices];
static const int regOffsetToOper[0x20];
void writeOplReg(int c, int r, int v);
void clear();
public:
OPL(Config::OplType type);
~OPL();
bool init();
void reset();
void write(int a, int v);
byte read(int a);
void writeReg(int r, int v);
};
const int OPL::voiceToOper0[OPL::kVoices] =
{ 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32 };
const int OPL::regOffsetToOper[0x20] =
{ 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1,
12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
OPL::OPL(Config::OplType type) : _type(type), _opl(nullptr), _iface(0) {
}
OPL::~OPL() {
stop();
if (_opl) {
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_RESET, nullptr);
snd_hwdep_close(_opl);
}
}
void OPL::clear() {
index[0] = index[1] = 0;
memset(_oper, 0, sizeof(_oper));
memset(_voice, 0, sizeof(_voice));
memset(&_params, 0, sizeof(_params));
for (int i = 0; i < kOperators; ++i) {
_oper[i].op = (i / 3) % 2;
_oper[i].voice = (i / 6) * 3 + (i % 3);
}
for (int i = 0; i < kVoices; ++i)
_voice[i].voice = i;
// For OPL3 hardware we need to set up the panning in OPL2 modes
if (_iface == SND_HWDEP_IFACE_OPL3) {
if (_type == Config::kDualOpl2) {
for (int i = 0; i < kOpl2Operators; ++i)
_oper[i].left = 1; // FIXME below
for (int i = kOpl2Operators; i < kOperators; ++i)
_oper[i].right = 1;
} else if (_type == Config::kOpl2) {
for (int i = 0; i < kOpl2Operators; ++i) {
_oper[i].left = 1;
_oper[i].right = 1;
}
}
}
}
bool OPL::init() {
clear();
int card = -1;
snd_ctl_t *ctl;
snd_hwdep_info_t *info;
snd_hwdep_info_alloca(&info);
int iface = SND_HWDEP_IFACE_OPL3;
if (_type == Config::kOpl2)
iface = SND_HWDEP_IFACE_OPL2;
// Look for OPL hwdep interface
while (!snd_card_next(&card) && card >= 0) {
int dev = -1;
Common::String name = Common::String::format("hw:%d", card);
if (snd_ctl_open(&ctl, name.c_str(), 0) < 0)
continue;
while (!snd_ctl_hwdep_next_device(ctl, &dev) && dev >= 0) {
name = Common::String::format("hw:%d,%d", card, dev);
if (snd_hwdep_open(&_opl, name.c_str(), SND_HWDEP_OPEN_WRITE) < 0)
continue;
if (!snd_hwdep_info(_opl, info)) {
int found = snd_hwdep_info_get_iface(info);
// OPL3 can be used for (Dual) OPL2 mode
if (found == iface || found == SND_HWDEP_IFACE_OPL3) {
snd_ctl_close(ctl);
_iface = found;
reset();
return true;
}
}
// Wrong interface, try next device
snd_hwdep_close(_opl);
_opl = nullptr;
}
snd_ctl_close(ctl);
}
return false;
}
void OPL::reset() {
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_RESET, nullptr);
if (_iface == SND_HWDEP_IFACE_OPL3)
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_MODE, (void *)SNDRV_DM_FM_MODE_OPL3);
clear();
// Sync up with the hardware
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_PARAMS, (void *)&_params);
for (uint i = 0; i < (_iface == SND_HWDEP_IFACE_OPL3 ? kVoices : kOpl2Voices); ++i)
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_PLAY_NOTE, (void *)&_voice[i]);
for (uint i = 0; i < (_iface == SND_HWDEP_IFACE_OPL3 ? kOperators : kOpl2Operators); ++i)
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[i]);
}
void OPL::write(int port, int val) {
val &= 0xff;
int chip = (port & 2) >> 1;
if (port & 1) {
switch(_type) {
case Config::kOpl2:
writeOplReg(0, index[0], val);
break;
case Config::kDualOpl2:
if (port & 8) {
writeOplReg(0, index[0], val);
writeOplReg(1, index[1], val);
} else
writeOplReg(chip, index[chip], val);
break;
case Config::kOpl3:
writeOplReg(chip, index[chip], val);
}
} else {
switch(_type) {
case Config::kOpl2:
index[0] = val;
break;
case Config::kDualOpl2:
if (port & 8) {
index[0] = val;
index[1] = val;
} else
index[chip] = val;
break;
case Config::kOpl3:
index[chip] = val;
}
}
}
byte OPL::read(int port) {
return 0;
}
void OPL::writeReg(int r, int v) {
switch (_type) {
case Config::kOpl2:
writeOplReg(0, r, v);
break;
case Config::kDualOpl2:
writeOplReg(0, r, v);
writeOplReg(1, r, v);
break;
case Config::kOpl3:
writeOplReg(r >= 0x100, r & 0xff, v);
}
}
void OPL::writeOplReg(int c, int r, int v) {
if (r == 0x04 && c == 1 && _type == Config::kOpl3) {
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_CONNECTION, reinterpret_cast<void *>(v & 0x3f));
} else if (r == 0x08 && c == 0) {
_params.kbd_split = (v >> 6) & 0x1;
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_PARAMS, (void *)&_params);
} else if (r == 0xbd && c == 0) {
_params.hihat = v & 0x1;
_params.cymbal = (v >> 1) & 0x1;
_params.tomtom = (v >> 2) & 0x1;
_params.snare = (v >> 3) & 0x1;
_params.bass = (v >> 4) & 0x1;
_params.rhythm = (v >> 5) & 0x1;
_params.vib_depth = (v >> 6) & 0x1;
_params.am_depth = (v >> 7) & 0x1;
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_PARAMS, (void *)&_params);
} else if (r < 0xa0 || r >= 0xe0) {
// Operator
int idx = regOffsetToOper[r & 0x1f];
if (idx == -1)
return;
if (c == 1)
idx += kOpl2Operators;
switch (r & 0xf0) {
case 0x20:
case 0x30:
_oper[idx].harmonic = v & 0xf;
_oper[idx].kbd_scale = (v >> 4) & 0x1;
_oper[idx].do_sustain = (v >> 5) & 0x1;
_oper[idx].vibrato = (v >> 6) & 0x1;
_oper[idx].am = (v >> 7) & 0x1;
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
break;
case 0x40:
case 0x50:
_oper[idx].volume = ~v & 0x3f;
_oper[idx].scale_level = (v >> 6) & 0x3;
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
break;
case 0x60:
case 0x70:
_oper[idx].decay = v & 0xf;
_oper[idx].attack = (v >> 4) & 0xf;
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
break;
case 0x80:
case 0x90:
_oper[idx].release = v & 0xf;
_oper[idx].sustain = (v >> 4) & 0xf;
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
break;
case 0xe0:
case 0xf0:
_oper[idx].waveform = v & (_type == Config::kOpl3 ? 0x7 : 0x3);
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
}
} else {
// Voice
int idx = r & 0xf;
if (idx >= kOpl2Voices)
return;
if (c == 1)
idx += kOpl2Voices;
int opIdx = voiceToOper0[idx];
switch (r & 0xf0) {
case 0xa0:
_voice[idx].fnum = (_voice[idx].fnum & 0x300) | (v & 0xff);
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_PLAY_NOTE, (void *)&_voice[idx]);
break;
case 0xb0:
_voice[idx].fnum = ((v << 8) & 0x300) | (_voice[idx].fnum & 0xff);
_voice[idx].octave = (v >> 2) & 0x7;
_voice[idx].key_on = (v >> 5) & 0x1;
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_PLAY_NOTE, (void *)&_voice[idx]);
break;
case 0xc0:
_oper[opIdx].connection = _oper[opIdx + 3].connection = v & 0x1;
_oper[opIdx].feedback = _oper[opIdx + 3].feedback = (v >> 1) & 0x7;
if (_type == Config::kOpl3) {
_oper[opIdx].left = _oper[opIdx + 3].left = (v >> 4) & 0x1;
_oper[opIdx].right = _oper[opIdx + 3].right = (v >> 5) & 0x1;
}
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[opIdx]);
}
}
}
OPL *create(Config::OplType type) {
return new OPL(type);
}
} // End of namespace ALSA
} // End of namespace OPL

343
audio/decoders/3do.cpp Normal file
View file

@ -0,0 +1,343 @@
/* 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.
*
*/
#include "common/textconsole.h"
#include "common/stream.h"
#include "common/util.h"
#include "audio/decoders/3do.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/adpcm_intern.h"
namespace Audio {
// Reuses ADPCM table
#define audio_3DO_ADP4_stepSizeTable Ima_ADPCMStream::_imaTable
#define audio_3DO_ADP4_stepSizeIndex ADPCMStream::_stepAdjustTable
RewindableAudioStream *make3DO_ADP4AudioStream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, uint32 *audioLengthMSecsPtr, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace) {
if (stereo) {
warning("make3DO_ADP4Stream(): stereo currently not supported");
return 0;
}
if (audioLengthMSecsPtr) {
// Caller requires the milliseconds of audio
uint32 audioLengthMSecs = stream->size() * 2 * 1000 / sampleRate; // 1 byte == 2 16-bit sample
if (stereo) {
audioLengthMSecs /= 2;
}
*audioLengthMSecsPtr = audioLengthMSecs;
}
return new Audio3DO_ADP4_Stream(stream, sampleRate, stereo, disposeAfterUse, persistentSpace);
}
Audio3DO_ADP4_Stream::Audio3DO_ADP4_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace)
: _sampleRate(sampleRate), _stereo(stereo),
_stream(stream, disposeAfterUse) {
_callerDecoderData = persistentSpace;
memset(&_initialDecoderData, 0, sizeof(_initialDecoderData));
_initialRead = true;
reset();
}
void Audio3DO_ADP4_Stream::reset() {
memcpy(&_curDecoderData, &_initialDecoderData, sizeof(_curDecoderData));
_streamBytesLeft = _stream->size();
_stream->seek(0);
}
bool Audio3DO_ADP4_Stream::rewind() {
reset();
return true;
}
int16 Audio3DO_ADP4_Stream::decodeSample(byte compressedNibble) {
int16 currentStep = audio_3DO_ADP4_stepSizeTable[_curDecoderData.stepIndex];
int32 decodedSample = _curDecoderData.lastSample;
int16 delta = currentStep >> 3;
if (compressedNibble & 1)
delta += currentStep >> 2;
if (compressedNibble & 2)
delta += currentStep >> 1;
if (compressedNibble & 4)
delta += currentStep;
if (compressedNibble & 8) {
decodedSample -= delta;
} else {
decodedSample += delta;
}
_curDecoderData.lastSample = CLIP<int32>(decodedSample, -32768, 32767);
_curDecoderData.stepIndex += audio_3DO_ADP4_stepSizeIndex[compressedNibble & 0x07];
_curDecoderData.stepIndex = CLIP<int16>(_curDecoderData.stepIndex, 0, ARRAYSIZE(audio_3DO_ADP4_stepSizeTable) - 1);
return _curDecoderData.lastSample;
}
// Writes the requested amount (or less) of samples into buffer and returns the amount of samples, that got written
int Audio3DO_ADP4_Stream::readBuffer(int16 *buffer, const int numSamples) {
int8 byteCache[AUDIO_3DO_CACHE_SIZE];
int8 *byteCachePtr = NULL;
int byteCacheSize = 0;
int requestedBytesLeft = 0;
int decodedSamplesCount = 0;
int8 compressedByte = 0;
if (endOfData())
return 0; // no more bytes left
if (_callerDecoderData) {
// copy caller decoder data over
memcpy(&_curDecoderData, _callerDecoderData, sizeof(_curDecoderData));
if (_initialRead) {
_initialRead = false;
memcpy(&_initialDecoderData, &_curDecoderData, sizeof(_initialDecoderData));
}
}
requestedBytesLeft = numSamples >> 1; // 1 byte for 2 16-bit sample
if (requestedBytesLeft > _streamBytesLeft)
requestedBytesLeft = _streamBytesLeft; // not enough bytes left
// in case caller requests an uneven amount of samples, we will return an even amount
// buffering, so that direct decoding of files and such runs way faster
while (requestedBytesLeft) {
if (requestedBytesLeft > AUDIO_3DO_CACHE_SIZE) {
byteCacheSize = AUDIO_3DO_CACHE_SIZE;
} else {
byteCacheSize = requestedBytesLeft;
}
requestedBytesLeft -= byteCacheSize;
_streamBytesLeft -= byteCacheSize;
// Fill our byte cache
_stream->read(byteCache, byteCacheSize);
byteCachePtr = byteCache;
// Mono
while (byteCacheSize) {
compressedByte = *byteCachePtr++;
byteCacheSize--;
buffer[decodedSamplesCount] = decodeSample(compressedByte >> 4);
decodedSamplesCount++;
buffer[decodedSamplesCount] = decodeSample(compressedByte & 0x0f);
decodedSamplesCount++;
}
}
if (_callerDecoderData) {
// copy caller decoder data back
memcpy(_callerDecoderData, &_curDecoderData, sizeof(_curDecoderData));
}
return decodedSamplesCount;
}
// ============================================================================
static int16 audio_3DO_SDX2_SquareTable[256] = {
-32768,-32258,-31752,-31250,-30752,-30258,-29768,-29282,-28800,-28322,
-27848,-27378,-26912,-26450,-25992,-25538,-25088,-24642,-24200,-23762,
-23328,-22898,-22472,-22050,-21632,-21218,-20808,-20402,-20000,-19602,
-19208,-18818,-18432,-18050,-17672,-17298,-16928,-16562,-16200,-15842,
-15488,-15138,-14792,-14450,-14112,-13778,-13448,-13122,-12800,-12482,
-12168,-11858,-11552,-11250,-10952,-10658,-10368,-10082, -9800, -9522,
-9248, -8978, -8712, -8450, -8192, -7938, -7688, -7442, -7200, -6962,
-6728, -6498, -6272, -6050, -5832, -5618, -5408, -5202, -5000, -4802,
-4608, -4418, -4232, -4050, -3872, -3698, -3528, -3362, -3200, -3042,
-2888, -2738, -2592, -2450, -2312, -2178, -2048, -1922, -1800, -1682,
-1568, -1458, -1352, -1250, -1152, -1058, -968, -882, -800, -722,
-648, -578, -512, -450, -392, -338, -288, -242, -200, -162,
-128, -98, -72, -50, -32, -18, -8, -2, 0, 2,
8, 18, 32, 50, 72, 98, 128, 162, 200, 242,
288, 338, 392, 450, 512, 578, 648, 722, 800, 882,
968, 1058, 1152, 1250, 1352, 1458, 1568, 1682, 1800, 1922,
2048, 2178, 2312, 2450, 2592, 2738, 2888, 3042, 3200, 3362,
3528, 3698, 3872, 4050, 4232, 4418, 4608, 4802, 5000, 5202,
5408, 5618, 5832, 6050, 6272, 6498, 6728, 6962, 7200, 7442,
7688, 7938, 8192, 8450, 8712, 8978, 9248, 9522, 9800, 10082,
10368, 10658, 10952, 11250, 11552, 11858, 12168, 12482, 12800, 13122,
13448, 13778, 14112, 14450, 14792, 15138, 15488, 15842, 16200, 16562,
16928, 17298, 17672, 18050, 18432, 18818, 19208, 19602, 20000, 20402,
20808, 21218, 21632, 22050, 22472, 22898, 23328, 23762, 24200, 24642,
25088, 25538, 25992, 26450, 26912, 27378, 27848, 28322, 28800, 29282,
29768, 30258, 30752, 31250, 31752, 32258
};
Audio3DO_SDX2_Stream::Audio3DO_SDX2_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpace)
: _sampleRate(sampleRate), _stereo(stereo),
_stream(stream, disposeAfterUse) {
_callerDecoderData = persistentSpace;
memset(&_initialDecoderData, 0, sizeof(_initialDecoderData));
_initialRead = true;
reset();
}
void Audio3DO_SDX2_Stream::reset() {
memcpy(&_curDecoderData, &_initialDecoderData, sizeof(_curDecoderData));
_streamBytesLeft = _stream->size();
_stream->seek(0);
}
bool Audio3DO_SDX2_Stream::rewind() {
reset();
return true;
}
// Writes the requested amount (or less) of samples into buffer and returns the amount of samples, that got written
int Audio3DO_SDX2_Stream::readBuffer(int16 *buffer, const int numSamples) {
int8 byteCache[AUDIO_3DO_CACHE_SIZE];
int8 *byteCachePtr = NULL;
int byteCacheSize = 0;
int requestedBytesLeft = numSamples; // 1 byte per 16-bit sample
int decodedSamplesCount = 0;
int8 compressedByte = 0;
uint8 squareTableOffset = 0;
int16 decodedSample = 0;
if (endOfData())
return 0; // no more bytes left
if (_stereo) {
// We expect numSamples to be even in case of Stereo audio
assert((numSamples & 1) == 0);
}
if (_callerDecoderData) {
// copy caller decoder data over
memcpy(&_curDecoderData, _callerDecoderData, sizeof(_curDecoderData));
if (_initialRead) {
_initialRead = false;
memcpy(&_initialDecoderData, &_curDecoderData, sizeof(_initialDecoderData));
}
}
requestedBytesLeft = numSamples;
if (requestedBytesLeft > _streamBytesLeft)
requestedBytesLeft = _streamBytesLeft; // not enough bytes left
// buffering, so that direct decoding of files and such runs way faster
while (requestedBytesLeft) {
if (requestedBytesLeft > AUDIO_3DO_CACHE_SIZE) {
byteCacheSize = AUDIO_3DO_CACHE_SIZE;
} else {
byteCacheSize = requestedBytesLeft;
}
requestedBytesLeft -= byteCacheSize;
_streamBytesLeft -= byteCacheSize;
// Fill our byte cache
_stream->read(byteCache, byteCacheSize);
byteCachePtr = byteCache;
if (!_stereo) {
// Mono
while (byteCacheSize) {
compressedByte = *byteCachePtr++;
byteCacheSize--;
squareTableOffset = compressedByte + 128;
if (!(compressedByte & 1))
_curDecoderData.lastSample1 = 0;
decodedSample = _curDecoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset];
_curDecoderData.lastSample1 = decodedSample;
buffer[decodedSamplesCount] = decodedSample;
decodedSamplesCount++;
}
} else {
// Stereo
while (byteCacheSize) {
compressedByte = *byteCachePtr++;
byteCacheSize--;
squareTableOffset = compressedByte + 128;
if (!(decodedSamplesCount & 1)) {
// First channel
if (!(compressedByte & 1))
_curDecoderData.lastSample1 = 0;
decodedSample = _curDecoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset];
_curDecoderData.lastSample1 = decodedSample;
} else {
// Second channel
if (!(compressedByte & 1))
_curDecoderData.lastSample2 = 0;
decodedSample = _curDecoderData.lastSample2 + audio_3DO_SDX2_SquareTable[squareTableOffset];
_curDecoderData.lastSample2 = decodedSample;
}
buffer[decodedSamplesCount] = decodedSample;
decodedSamplesCount++;
}
}
}
if (_callerDecoderData) {
// copy caller decoder data back
memcpy(_callerDecoderData, &_curDecoderData, sizeof(_curDecoderData));
}
return decodedSamplesCount;
}
RewindableAudioStream *make3DO_SDX2AudioStream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, uint32 *audioLengthMSecsPtr, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpace) {
if (stereo) {
if (stream->size() & 1) {
warning("make3DO_SDX2Stream(): stereo data is uneven size");
return 0;
}
}
if (audioLengthMSecsPtr) {
// Caller requires the milliseconds of audio
uint32 audioLengthMSecs = stream->size() * 1000 / sampleRate; // 1 byte == 1 16-bit sample
if (stereo) {
audioLengthMSecs /= 2;
}
*audioLengthMSecsPtr = audioLengthMSecs;
}
return new Audio3DO_SDX2_Stream(stream, sampleRate, stereo, disposeAfterUse, persistentSpace);
}
} // End of namespace Audio

158
audio/decoders/3do.h Normal file
View file

@ -0,0 +1,158 @@
/* 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.
*
*/
/**
* @file
* Sound decoder used in engines:
* - sherlock (3DO version of Serrated Scalpel)
*/
#ifndef AUDIO_3DO_SDX2_H
#define AUDIO_3DO_SDX2_H
#include "common/scummsys.h"
#include "common/types.h"
#include "common/substream.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
namespace Common {
class SeekableReadStream;
}
namespace Audio {
class SeekableAudioStream;
// amount of bytes to be used within the decoder classes as buffers
#define AUDIO_3DO_CACHE_SIZE 1024
// persistent spaces
struct audio_3DO_ADP4_PersistentSpace {
int16 lastSample;
int16 stepIndex;
};
struct audio_3DO_SDX2_PersistentSpace {
int16 lastSample1;
int16 lastSample2;
};
class Audio3DO_ADP4_Stream : public RewindableAudioStream {
public:
Audio3DO_ADP4_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace);
protected:
const uint16 _sampleRate;
const bool _stereo;
Common::DisposablePtr<Common::SeekableReadStream> _stream;
int32 _streamBytesLeft;
void reset();
bool rewind();
bool endOfData() const { return (_stream->pos() >= _stream->size()); }
bool isStereo() const { return _stereo; }
int getRate() const { return _sampleRate; }
int readBuffer(int16 *buffer, const int numSamples);
bool _initialRead;
audio_3DO_ADP4_PersistentSpace *_callerDecoderData;
audio_3DO_ADP4_PersistentSpace _initialDecoderData;
audio_3DO_ADP4_PersistentSpace _curDecoderData;
private:
int16 decodeSample(byte compressedNibble);
};
class Audio3DO_SDX2_Stream : public RewindableAudioStream {
public:
Audio3DO_SDX2_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpacePtr);
protected:
const uint16 _sampleRate;
const bool _stereo;
Common::DisposablePtr<Common::SeekableReadStream> _stream;
int32 _streamBytesLeft;
void reset();
bool rewind();
bool endOfData() const { return (_stream->pos() >= _stream->size()); }
bool isStereo() const { return _stereo; }
int getRate() const { return _sampleRate; }
int readBuffer(int16 *buffer, const int numSamples);
bool _initialRead;
audio_3DO_SDX2_PersistentSpace *_callerDecoderData;
audio_3DO_SDX2_PersistentSpace _initialDecoderData;
audio_3DO_SDX2_PersistentSpace _curDecoderData;
};
/**
* Try to decode 3DO ADP4 data from the given seekable stream and create a SeekableAudioStream
* from that data.
*
* @param stream the SeekableReadStream from which to read the 3DO SDX2 data
* @sampleRate sample rate
* @stereo if it's stereo or mono
* @audioLengthMSecsPtr pointer to a uint32 variable, that is supposed to get the length of the audio in milliseconds
* @disposeAfterUse disposeAfterUse whether to delete the stream after use
* @persistentSpacePtr pointer to the persistent space structure
* @return a new SeekableAudioStream, or NULL, if an error occurred
*/
RewindableAudioStream *make3DO_ADP4AudioStream(
Common::SeekableReadStream *stream,
uint16 sampleRate,
bool stereo,
uint32 *audioLengthMSecsPtr = NULL, // returns the audio length in milliseconds
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES,
audio_3DO_ADP4_PersistentSpace *persistentSpacePtr = NULL
);
/**
* Try to decode 3DO SDX2 data from the given seekable stream and create a SeekableAudioStream
* from that data.
*
* @param stream the SeekableReadStream from which to read the 3DO SDX2 data
* @sampleRate sample rate
* @stereo if it's stereo or mono
* @audioLengthMSecsPtr pointer to a uint32 variable, that is supposed to get the length of the audio in milliseconds
* @disposeAfterUse disposeAfterUse whether to delete the stream after use
* @persistentSpacePtr pointer to the persistent space structure
* @return a new SeekableAudioStream, or NULL, if an error occurred
*/
RewindableAudioStream *make3DO_SDX2AudioStream(
Common::SeekableReadStream *stream,
uint16 sampleRate,
bool stereo,
uint32 *audioLengthMSecsPtr = NULL, // returns the audio length in milliseconds
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES,
audio_3DO_SDX2_PersistentSpace *persistentSpacePtr = NULL
);
} // End of namespace Audio
#endif

View file

@ -24,16 +24,19 @@
* 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.
* Also partially based on libav's aiffdec.c
*/
#include "common/debug.h"
#include "common/endian.h"
#include "common/stream.h"
#include "common/substream.h"
#include "common/textconsole.h"
#include "audio/audiostream.h"
#include "audio/decoders/aiff.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/3do.h"
namespace Audio {
@ -62,23 +65,34 @@ uint32 readExtended(Common::SeekableReadStream &stream) {
return mantissa;
}
bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags) {
byte buf[4];
// AIFF versions
static const uint32 kVersionAIFF = MKTAG('A', 'I', 'F', 'F');
static const uint32 kVersionAIFC = MKTAG('A', 'I', 'F', 'C');
stream.read(buf, 4);
if (memcmp(buf, "FORM", 4) != 0) {
warning("loadAIFFFromStream: No 'FORM' header");
return false;
// Codecs
static const uint32 kCodecPCM = MKTAG('N', 'O', 'N', 'E'); // very original
RewindableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
if (stream->readUint32BE() != MKTAG('F', 'O', 'R', 'M')) {
warning("makeAIFFStream: No 'FORM' header");
if (disposeAfterUse == DisposeAfterUse::YES)
delete stream;
return 0;
}
stream.readUint32BE();
stream->readUint32BE(); // file size
// This could be AIFC, but we don't handle that case.
uint32 version = stream->readUint32BE();
stream.read(buf, 4);
if (memcmp(buf, "AIFF", 4) != 0) {
warning("loadAIFFFromStream: No 'AIFF' header");
return false;
if (version != kVersionAIFF && version != kVersionAIFC) {
warning("makeAIFFStream: No 'AIFF' or 'AIFC' header");
if (disposeAfterUse == DisposeAfterUse::YES)
delete stream;
return 0;
}
// From here on, we only care about the COMM and SSND chunks, which are
@ -87,95 +101,131 @@ bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate
bool foundCOMM = false;
bool foundSSND = false;
uint16 numChannels = 0, bitsPerSample = 0;
uint32 numSampleFrames = 0, offset = 0, blockSize = 0, soundOffset = 0;
uint16 channels = 0, bitsPerSample = 0;
uint32 blockAlign = 0, rate = 0;
uint32 codec = kCodecPCM; // AIFF default
Common::SeekableReadStream *dataStream = 0;
while (!(foundCOMM && foundSSND) && !stream.err() && !stream.eos()) {
uint32 length, pos;
while (!(foundCOMM && foundSSND) && !stream->err() && !stream->eos()) {
uint32 tag = stream->readUint32BE();
uint32 length = stream->readUint32BE();
uint32 pos = stream->pos();
stream.read(buf, 4);
length = stream.readUint32BE();
pos = stream.pos();
if (stream->eos() || stream->err())
break;
if (memcmp(buf, "COMM", 4) == 0) {
switch (tag) {
case MKTAG('C', 'O', 'M', 'M'):
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) {
channels = stream->readUint16BE();
/* frameCount = */ stream->readUint32BE();
bitsPerSample = stream->readUint16BE();
rate = readExtended(*stream);
if (version == kVersionAIFC)
codec = stream->readUint32BE();
break;
case MKTAG('S', 'S', 'N', 'D'):
foundSSND = true;
offset = stream.readUint32BE();
blockSize = stream.readUint32BE();
soundOffset = stream.pos();
/* uint32 offset = */ stream->readUint32BE();
blockAlign = stream->readUint32BE();
dataStream = new Common::SeekableSubReadStream(stream, stream->pos(), stream->pos() + length - 8, disposeAfterUse);
break;
case MKTAG('F', 'V', 'E', 'R'):
switch (stream->readUint32BE()) {
case 0:
version = kVersionAIFF;
break;
case 0xA2805140:
version = kVersionAIFC;
break;
default:
warning("Unknown AIFF version chunk version");
break;
}
break;
case MKTAG('w', 'a', 'v', 'e'):
warning("Found unhandled AIFF-C extra data chunk");
if (!dataStream && disposeAfterUse == DisposeAfterUse::YES)
delete stream;
delete dataStream;
return 0;
default:
debug(1, "Skipping AIFF '%s' chunk", tag2str(tag));
break;
}
stream.seek(pos + length);
stream->seek(pos + length + (length & 1)); // ensure we're also word-aligned
}
if (!foundCOMM) {
warning("loadAIFFFromStream: Cound not find 'COMM' chunk");
return false;
warning("makeAIFFStream: Cound not find 'COMM' chunk");
if (!dataStream && disposeAfterUse == DisposeAfterUse::YES)
delete stream;
delete dataStream;
return 0;
}
if (!foundSSND) {
warning("loadAIFFFromStream: Cound not find 'SSND' chunk");
return false;
warning("makeAIFFStream: Cound not find 'SSND' chunk");
if (disposeAfterUse == DisposeAfterUse::YES)
delete stream;
return 0;
}
// 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;
if (channels < 1 || channels > 2) {
warning("makeAIFFStream: Only 1 or 2 channels are supported, not %d", channels);
delete dataStream;
return 0;
}
data = (byte *)malloc(size);
assert(data);
stream->read(data, size);
// Seek to the start of dataStream, required for at least FileStream
dataStream->seek(0);
if (disposeAfterUse == DisposeAfterUse::YES)
delete stream;
switch (codec) {
case kCodecPCM:
case MKTAG('t', 'w', 'o', 's'):
case MKTAG('s', 'o', 'w', 't'): {
// PCM samples are always signed.
byte rawFlags = 0;
if (bitsPerSample == 16)
rawFlags |= Audio::FLAG_16BITS;
if (channels == 2)
rawFlags |= Audio::FLAG_STEREO;
if (codec == MKTAG('s', 'o', 'w', 't'))
rawFlags |= Audio::FLAG_LITTLE_ENDIAN;
// Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES.
return makeRawStream(data, size, rate, flags);
return makeRawStream(dataStream, rate, rawFlags);
}
case MKTAG('i', 'm', 'a', '4'):
// TODO: Use QT IMA ADPCM
warning("Unhandled AIFF-C QT IMA ADPCM compression");
break;
case MKTAG('Q', 'D', 'M', '2'):
// TODO: Need to figure out how to integrate this
// (But hopefully never needed)
warning("Unhandled AIFF-C QDM2 compression");
break;
case MKTAG('A', 'D', 'P', '4'):
// ADP4 on 3DO
return make3DO_ADP4AudioStream(dataStream, rate, channels == 2);
case MKTAG('S', 'D', 'X', '2'):
// SDX2 on 3DO
return make3DO_SDX2AudioStream(dataStream, rate, channels == 2);
default:
warning("Unhandled AIFF-C compression tag '%s'", tag2str(codec));
}
delete dataStream;
return 0;
}
} // End of namespace Audio

View file

@ -23,6 +23,7 @@
/**
* @file
* Sound decoder used in engines:
* - bbvs
* - pegasus
* - saga
* - sci
@ -41,28 +42,17 @@ 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.
*/
extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags);
class RewindableAudioStream;
/**
* 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(
RewindableAudioStream *makeAIFFStream(
Common::SeekableReadStream *stream,
DisposeAfterUse::Flag disposeAfterUse);

View file

@ -246,6 +246,23 @@ void MP3Stream::initStream() {
_inStream->seek(0, SEEK_SET);
_curTime = mad_timer_zero;
_posInFrame = 0;
// Skip ID3 TAG if any
// ID3v1 (beginning with with 'TAG') is located at the end of files. So we can ignore those.
// ID3v2 can be located at the start of files and begins with a 10 bytes header, the first 3 bytes being 'ID3'.
// The tag size is coded on the last 4 bytes of the 10 bytes header as a 32 bit synchsafe integer.
// See http://id3.org/id3v2.4.0-structure for details.
char data[10];
_inStream->read(data, 10);
if (data[0] == 'I' && data[1] == 'D' && data[2] == '3') {
uint32 size = data[9] + 128 * (data[8] + 128 * (data[7] + 128 * data[6]));
// This size does not include an optional 10 bytes footer. Check if it is present.
if (data[5] & 0x10)
size += 10;
debug("Skipping ID3 TAG (%d bytes)", size + 10);
_inStream->seek(size, SEEK_CUR);
} else
_inStream->seek(0, SEEK_SET);
// Update state
_state = MP3_STATE_READY;

View file

@ -241,6 +241,15 @@ void QuickTimeAudioDecoder::QuickTimeAudioTrack::queueAudio(const Timestamp &len
// If we have any samples that we need to skip (ie. we seeked into
// the middle of a chunk), skip them here.
if (_skipSamples != Timestamp()) {
if (_skipSamples > chunkLength) {
// If the amount we need to skip is greater than the size
// of the chunk, just skip it altogether.
_curMediaPos = _curMediaPos + chunkLength;
_skipSamples = _skipSamples - chunkLength;
delete stream;
continue;
}
skipSamples(_skipSamples, stream);
_curMediaPos = _curMediaPos + _skipSamples;
chunkLength = chunkLength - _skipSamples;

View file

@ -23,15 +23,25 @@
/**
* @file
* Sound decoder used in engines:
* - access
* - agos
* - cge
* - cge2
* - fullpipe
* - gob
* - hopkins
* - mohawk
* - prince
* - saga
* - sci
* - scumm
* - sherlock
* - sword1
* - sword2
* - tony
* - tucker
* - wintermute
* - zvision
*/
#ifndef AUDIO_WAVE_H

View file

@ -22,21 +22,33 @@
#include "audio/fmopl.h"
#include "audio/mixer.h"
#include "audio/softsynth/opl/dosbox.h"
#include "audio/softsynth/opl/mame.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/timer.h"
#include "common/translation.h"
namespace OPL {
// Factory functions
#ifdef USE_ALSA
namespace ALSA {
OPL *create(Config::OplType type);
} // End of namespace ALSA
#endif // USE_ALSA
// Config implementation
enum OplEmulator {
kAuto = 0,
kMame = 1,
kDOSBox = 2
kDOSBox = 2,
kALSA = 3
};
OPL::OPL() {
@ -50,6 +62,9 @@ const Config::EmulatorDescription Config::_drivers[] = {
{ "mame", _s("MAME OPL emulator"), kMame, kFlagOpl2 },
#ifndef DISABLE_DOSBOX_OPL
{ "db", _s("DOSBox OPL emulator"), kDOSBox, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 },
#endif
#ifdef USE_ALSA
{ "alsa", _s("ALSA Direct FM"), kALSA, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 },
#endif
{ 0, 0, 0, 0 }
};
@ -63,6 +78,15 @@ Config::DriverId Config::parse(const Common::String &name) {
return -1;
}
const Config::EmulatorDescription *Config::findDriver(DriverId id) {
for (int i = 0; _drivers[i].name; ++i) {
if (_drivers[i].id == id)
return &_drivers[i];
}
return 0;
}
Config::DriverId Config::detect(OplType type) {
uint32 flags = 0;
switch (type) {
@ -80,12 +104,21 @@ Config::DriverId Config::detect(OplType type) {
}
DriverId drv = parse(ConfMan.get("opl_driver"));
if (drv == kAuto) {
// Since the "auto" can be explicitly set for a game, and this
// driver shows up in the GUI as "<default>", check if there is
// a global setting for it before resorting to auto-detection.
drv = parse(ConfMan.get("opl_driver", Common::ConfigManager::kApplicationDomain));
}
// When a valid driver is selected, check whether it supports
// the requested OPL chip.
if (drv != -1 && drv != kAuto) {
const EmulatorDescription *driverDesc = findDriver(drv);
// If the chip is supported, just use the driver.
if ((flags & _drivers[drv].flags)) {
if (!driverDesc) {
warning("The selected OPL driver %d could not be found", drv);
} else if ((flags & driverDesc->flags)) {
return drv;
} else {
// Else we will output a warning and just
@ -145,6 +178,11 @@ OPL *Config::create(DriverId driver, OplType type) {
return new DOSBox::OPL(type);
#endif
#ifdef USE_ALSA
case kALSA:
return ALSA::create(type);
#endif
default:
warning("Unsupported OPL emulator %d", driver);
// TODO: Maybe we should add some dummy emulator too, which just outputs
@ -153,43 +191,143 @@ OPL *Config::create(DriverId driver, OplType type) {
}
}
void OPL::start(TimerCallback *callback, int timerFrequency) {
_callback.reset(callback);
startCallbacks(timerFrequency);
}
void OPL::stop() {
stopCallbacks();
_callback.reset();
}
bool OPL::_hasInstance = false;
} // End of namespace OPL
void OPLDestroy(FM_OPL *OPL) {
delete OPL;
RealOPL::RealOPL() : _baseFreq(0), _remainingTicks(0) {
}
void OPLResetChip(FM_OPL *OPL) {
OPL->reset();
RealOPL::~RealOPL() {
// Stop callbacks, just in case. If it's still playing at this
// point, there's probably a bigger issue, though. The subclass
// needs to call stop() or the pointer can still use be used in
// the mixer thread at the same time.
stop();
}
void OPLWrite(FM_OPL *OPL, int a, int v) {
OPL->write(a, v);
void RealOPL::setCallbackFrequency(int timerFrequency) {
stopCallbacks();
startCallbacks(timerFrequency);
}
unsigned char OPLRead(FM_OPL *OPL, int a) {
return OPL->read(a);
void RealOPL::startCallbacks(int timerFrequency) {
_baseFreq = timerFrequency;
assert(_baseFreq > 0);
// We can't request more a timer faster than 100Hz. We'll handle this by calling
// the proc multiple times in onTimer() later on.
if (timerFrequency > kMaxFreq)
timerFrequency = kMaxFreq;
_remainingTicks = 0;
g_system->getTimerManager()->installTimerProc(timerProc, 1000000 / timerFrequency, this, "RealOPL");
}
void OPLWriteReg(FM_OPL *OPL, int r, int v) {
OPL->writeReg(r, v);
void RealOPL::stopCallbacks() {
g_system->getTimerManager()->removeTimerProc(timerProc);
_baseFreq = 0;
_remainingTicks = 0;
}
void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length) {
OPL->readBuffer(buffer, length);
void RealOPL::timerProc(void *refCon) {
static_cast<RealOPL *>(refCon)->onTimer();
}
FM_OPL *makeAdLibOPL(int rate) {
FM_OPL *opl = OPL::Config::create();
void RealOPL::onTimer() {
uint callbacks = 1;
if (opl) {
if (!opl->init(rate)) {
delete opl;
opl = 0;
}
if (_baseFreq > kMaxFreq) {
// We run faster than our max, so run the callback multiple
// times to approximate the actual timer callback frequency.
uint totalTicks = _baseFreq + _remainingTicks;
callbacks = totalTicks / kMaxFreq;
_remainingTicks = totalTicks % kMaxFreq;
}
return opl;
// Call the callback multiple times. The if is on the inside of the
// loop in case the callback removes itself.
for (uint i = 0; i < callbacks; i++)
if (_callback && _callback->isValid())
(*_callback)();
}
EmulatedOPL::EmulatedOPL() :
_nextTick(0),
_samplesPerTick(0),
_baseFreq(0),
_handle(new Audio::SoundHandle()) {
}
EmulatedOPL::~EmulatedOPL() {
// Stop callbacks, just in case. If it's still playing at this
// point, there's probably a bigger issue, though. The subclass
// needs to call stop() or the pointer can still use be used in
// the mixer thread at the same time.
stop();
delete _handle;
}
int EmulatedOPL::readBuffer(int16 *buffer, 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(buffer, step * stereoFactor);
_nextTick -= step << FIXP_SHIFT;
if (!(_nextTick >> FIXP_SHIFT)) {
if (_callback && _callback->isValid())
(*_callback)();
_nextTick += _samplesPerTick;
}
buffer += step * stereoFactor;
len -= step;
} while (len);
return numSamples;
}
int EmulatedOPL::getRate() const {
return g_system->getMixer()->getOutputRate();
}
void EmulatedOPL::startCallbacks(int timerFrequency) {
setCallbackFrequency(timerFrequency);
g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, _handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}
void EmulatedOPL::stopCallbacks() {
g_system->getMixer()->stopHandle(*_handle);
}
void EmulatedOPL::setCallbackFrequency(int timerFrequency) {
_baseFreq = timerFrequency;
assert(_baseFreq != 0);
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;
}
} // End of namespace OPL

View file

@ -23,8 +23,16 @@
#ifndef AUDIO_FMOPL_H
#define AUDIO_FMOPL_H
#include "audio/audiostream.h"
#include "common/func.h"
#include "common/ptr.h"
#include "common/scummsys.h"
namespace Audio {
class SoundHandle;
}
namespace Common {
class String;
}
@ -70,6 +78,12 @@ public:
*/
static DriverId parse(const Common::String &name);
/**
* @return The driver description for the given id or 0 in case it is not
* available.
*/
static const EmulatorDescription *findDriver(DriverId id);
/**
* Detects a driver for the specific type.
*
@ -92,6 +106,14 @@ private:
static const EmulatorDescription _drivers[];
};
/**
* The type of the OPL timer callback functor.
*/
typedef Common::Functor0<void> TimerCallback;
/**
* A representation of a Yamaha OPL chip.
*/
class OPL {
private:
static bool _hasInstance;
@ -102,10 +124,9 @@ public:
/**
* Initializes the OPL emulator.
*
* @param rate output sample rate
* @return true on success, false on failure
*/
virtual bool init(int rate) = 0;
virtual bool init() = 0;
/**
* Reinitializes the OPL emulator
@ -139,6 +160,101 @@ public:
*/
virtual void writeReg(int r, int v) = 0;
/**
* Start the OPL with callbacks.
*/
void start(TimerCallback *callback, int timerFrequency = kDefaultCallbackFrequency);
/**
* Stop the OPL
*/
void stop();
/**
* Change the callback frequency. This must only be called from a
* timer proc.
*/
virtual void setCallbackFrequency(int timerFrequency) = 0;
enum {
/**
* The default callback frequency that start() uses
*/
kDefaultCallbackFrequency = 250
};
protected:
/**
* Start the callbacks.
*/
virtual void startCallbacks(int timerFrequency) = 0;
/**
* Stop the callbacks.
*/
virtual void stopCallbacks() = 0;
/**
* The functor for callbacks.
*/
Common::ScopedPtr<TimerCallback> _callback;
};
/**
* An OPL that represents a real OPL, as opposed to an emulated one.
*
* This will use an actual timer instead of using one calculated from
* the number of samples in an AudioStream::readBuffer call.
*/
class RealOPL : public OPL {
public:
RealOPL();
virtual ~RealOPL();
// OPL API
void setCallbackFrequency(int timerFrequency);
protected:
// OPL API
void startCallbacks(int timerFrequency);
void stopCallbacks();
private:
static void timerProc(void *refCon);
void onTimer();
uint _baseFreq;
uint _remainingTicks;
enum {
kMaxFreq = 100
};
};
/**
* An OPL that represents an emulated OPL.
*
* This will send callbacks based on the number of samples
* decoded in readBuffer().
*/
class EmulatedOPL : public OPL, protected Audio::AudioStream {
public:
EmulatedOPL();
virtual ~EmulatedOPL();
// OPL API
void setCallbackFrequency(int timerFrequency);
// AudioStream API
int readBuffer(int16 *buffer, const int numSamples);
int getRate() const;
bool endOfData() const { return false; }
protected:
// OPL API
void startCallbacks(int timerFrequency);
void stopCallbacks();
/**
* Read up to 'length' samples.
*
@ -149,33 +265,21 @@ public:
* 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;
virtual void generateSamples(int16 *buffer, int numSamples) = 0;
/**
* Returns whether the setup OPL mode is stereo or not
*/
virtual bool isStereo() const = 0;
private:
int _baseFreq;
enum {
FIXP_SHIFT = 16
};
int _nextTick;
int _samplesPerTick;
Audio::SoundHandle *_handle;
};
} // 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

View file

@ -370,6 +370,7 @@ public:
public:
typedef void (*XMidiCallbackProc)(byte eventData, void *refCon);
typedef void (*XMidiNewTimbreListProc)(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize);
MidiParser();
virtual ~MidiParser() { allNotesOff(); }
@ -395,7 +396,7 @@ public:
static void defaultXMidiCallback(byte eventData, void *refCon);
static MidiParser *createParser_SMF();
static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0);
static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0, XMidiNewTimbreListProc newTimbreListProc = NULL, MidiDriver_BASE *newTimbreListDriver = NULL);
static MidiParser *createParser_QT();
static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); }
};

View file

@ -43,6 +43,22 @@ protected:
XMidiCallbackProc _callbackProc;
void *_callbackData;
// TODO:
// This should possibly get cleaned up at some point, but it's very tricks.
// We need to support XMIDI TIMB for 7th guest, which uses
// Miles Audio drivers. The MT32 driver needs to get the TIMB chunk, so that it
// can install all required timbres before the song starts playing.
// But we can't easily implement this directly like for example creating
// a special Miles Audio class for usage in this XMIDI-class, because other engines use this
// XMIDI-parser but w/o using Miles Audio drivers.
XMidiNewTimbreListProc _newTimbreListProc;
MidiDriver_BASE *_newTimbreListDriver;
byte *_tracksTimbreList[120]; ///< Timbre-List for each track.
uint32 _tracksTimbreListSize[120]; ///< Size of the Timbre-List for each track.
byte *_activeTrackTimbreList;
uint32 _activeTrackTimbreListSize;
protected:
uint32 readVLQ2(byte * &data);
void parseNextEvent(EventInfo &info);
@ -53,7 +69,17 @@ protected:
}
public:
MidiParser_XMIDI(XMidiCallbackProc proc, void *data) : _callbackProc(proc), _callbackData(data), _loopCount(-1) {}
MidiParser_XMIDI(XMidiCallbackProc proc, void *data, XMidiNewTimbreListProc newTimbreListProc, MidiDriver_BASE *newTimbreListDriver) {
_callbackProc = proc;
_callbackData = data;
_loopCount = -1;
_newTimbreListProc = newTimbreListProc;
_newTimbreListDriver = newTimbreListDriver;
memset(_tracksTimbreList, 0, sizeof(_tracksTimbreList));
memset(_tracksTimbreListSize, 0, sizeof(_tracksTimbreListSize));
_activeTrackTimbreList = NULL;
_activeTrackTimbreListSize = 0;
}
~MidiParser_XMIDI() { }
bool loadMusic(byte *data, uint32 size);
@ -322,11 +348,16 @@ bool MidiParser_XMIDI::loadMusic(byte *data, uint32 size) {
// 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.
// Custom timbres
// chunk data is as follows:
// UINT16LE timbre count (amount of custom timbres used by this track)
// BYTE patchId
// BYTE bankId
// * timbre count
pos += 4;
len = read4high(pos);
_tracksTimbreList[tracksRead] = pos; // Skip the length bytes
_tracksTimbreListSize[tracksRead] = len;
pos += (len + 1) & ~1;
} else if (!memcmp(pos, "EVNT", 4)) {
// Ahh! What we're looking for at last.
@ -350,6 +381,12 @@ bool MidiParser_XMIDI::loadMusic(byte *data, uint32 size) {
resetTracking();
setTempo(500000);
setTrack(0);
_activeTrackTimbreList = _tracksTimbreList[0];
_activeTrackTimbreListSize = _tracksTimbreListSize[0];
if (_newTimbreListProc)
_newTimbreListProc(_newTimbreListDriver, _activeTrackTimbreList, _activeTrackTimbreListSize);
return true;
}
@ -360,6 +397,6 @@ 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);
MidiParser *MidiParser::createParser_XMIDI(XMidiCallbackProc proc, void *data, XMidiNewTimbreListProc newTimbreListProc, MidiDriver_BASE *newTimbreListDriver) {
return new MidiParser_XMIDI(proc, data, newTimbreListProc, newTimbreListDriver);
}

83
audio/miles.h Normal file
View file

@ -0,0 +1,83 @@
/* 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.
*
*/
#ifndef AUDIO_MILES_MIDIDRIVER_H
#define AUDIO_MILES_MIDIDRIVER_H
#include "audio/mididrv.h"
#include "common/error.h"
#include "common/stream.h"
namespace Audio {
#define MILES_MIDI_CHANNEL_COUNT 16
// Miles Audio supported controllers for control change messages
#define MILES_CONTROLLER_SELECT_PATCH_BANK 114
#define MILES_CONTROLLER_PROTECT_VOICE 112
#define MILES_CONTROLLER_PROTECT_TIMBRE 113
#define MILES_CONTROLLER_MODULATION 1
#define MILES_CONTROLLER_VOLUME 7
#define MILES_CONTROLLER_EXPRESSION 11
#define MILES_CONTROLLER_PANNING 10
#define MILES_CONTROLLER_SUSTAIN 64
#define MILES_CONTROLLER_PITCH_RANGE 6
#define MILES_CONTROLLER_RESET_ALL 121
#define MILES_CONTROLLER_ALL_NOTES_OFF 123
#define MILES_CONTROLLER_PATCH_REVERB 59
#define MILES_CONTROLLER_PATCH_BENDER 60
#define MILES_CONTROLLER_REVERB_MODE 61
#define MILES_CONTROLLER_REVERB_TIME 62
#define MILES_CONTROLLER_REVERB_LEVEL 63
#define MILES_CONTROLLER_RHYTHM_KEY_TIMBRE 58
// 3 SysEx controllers, each range 5
// 32-36 for 1st queue
// 37-41 for 2nd queue
// 42-46 for 3rd queue
#define MILES_CONTROLLER_SYSEX_RANGE_BEGIN 32
#define MILES_CONTROLLER_SYSEX_RANGE_END 46
#define MILES_CONTROLLER_SYSEX_QUEUE_COUNT 3
#define MILES_CONTROLLER_SYSEX_QUEUE_SIZE 32
#define MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1 0
#define MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2 1
#define MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3 2
#define MILES_CONTROLLER_SYSEX_COMMAND_DATA 3
#define MILES_CONTROLLER_SYSEX_COMMAND_SEND 4
#define MILES_CONTROLLER_XMIDI_RANGE_BEGIN 110
#define MILES_CONTROLLER_XMIDI_RANGE_END 120
// Miles Audio actually used 0x4000, because they didn't shift the 2 bytes properly
#define MILES_PITCHBENDER_DEFAULT 0x2000
extern MidiDriver *MidiDriver_Miles_AdLib_create(const Common::String &filenameAdLib, const Common::String &filenameOPL3, Common::SeekableReadStream *streamAdLib = nullptr, Common::SeekableReadStream *streamOPL3 = nullptr);
extern MidiDriver *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename);
extern void MidiDriver_Miles_MT32_processXMIDITimbreChunk(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize);
} // End of namespace Audio
#endif // AUDIO_MILES_MIDIDRIVER_H

1274
audio/miles_adlib.cpp Normal file

File diff suppressed because it is too large Load diff

912
audio/miles_mt32.cpp Normal file
View file

@ -0,0 +1,912 @@
/* 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.
*
*/
#include "audio/miles.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/mutex.h"
#include "common/system.h"
#include "common/textconsole.h"
namespace Audio {
// Miles Audio MT32 driver
//
#define MILES_MT32_PATCHES_COUNT 128
#define MILES_MT32_CUSTOMTIMBRE_COUNT 64
#define MILES_MT32_TIMBREBANK_STANDARD_ROLAND 0
#define MILES_MT32_TIMBREBANK_MELODIC_MODULE 127
#define MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE 14
#define MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE 58
#define MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT 4
#define MILES_MT32_PATCHDATA_TOTAL_SIZE (MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + (MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE * MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT))
#define MILES_MT32_SYSEX_TERMINATOR 0xFF
struct MilesMT32InstrumentEntry {
byte bankId;
byte patchId;
byte commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + 1];
byte partialParameters[MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE + 1];
};
const byte milesMT32SysExResetParameters[] = {
0x01, MILES_MT32_SYSEX_TERMINATOR
};
const byte milesMT32SysExChansSetup[] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, MILES_MT32_SYSEX_TERMINATOR
};
const byte milesMT32SysExPartialReserveTable[] = {
0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04, MILES_MT32_SYSEX_TERMINATOR
};
const byte milesMT32SysExInitReverb[] = {
0x00, 0x03, 0x02, MILES_MT32_SYSEX_TERMINATOR // Reverb mode 0, reverb time 3, reverb level 2
};
class MidiDriver_Miles_MT32 : public MidiDriver {
public:
MidiDriver_Miles_MT32(MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount);
virtual ~MidiDriver_Miles_MT32();
// MidiDriver
int open();
void close();
bool isOpen() const { return _isOpen; }
void send(uint32 b);
MidiChannel *allocateChannel() {
if (_driver)
return _driver->allocateChannel();
return NULL;
}
MidiChannel *getPercussionChannel() {
if (_driver)
return _driver->getPercussionChannel();
return NULL;
}
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
if (_driver)
_driver->setTimerCallback(timer_param, timer_proc);
}
uint32 getBaseTempo() {
if (_driver) {
return _driver->getBaseTempo();
}
return 1000000 / _baseFreq;
}
protected:
Common::Mutex _mutex;
MidiDriver *_driver;
bool _MT32;
bool _nativeMT32;
bool _isOpen;
int _baseFreq;
public:
void processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize);
private:
void resetMT32();
void MT32SysEx(const uint32 targetAddress, const byte *dataPtr);
uint32 calculateSysExTargetAddress(uint32 baseAddress, uint32 index);
void writeRhythmSetup(byte note, byte customTimbreId);
void writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId);
void writePatchByte(byte patchId, byte index, byte patchValue);
void writeToSystemArea(byte index, byte value);
void controlChange(byte midiChannel, byte controllerNumber, byte controllerValue);
void programChange(byte midiChannel, byte patchId);
const MilesMT32InstrumentEntry *searchCustomInstrument(byte patchBank, byte patchId);
int16 searchCustomTimbre(byte patchBank, byte patchId);
void setupPatch(byte patchBank, byte patchId);
int16 installCustomTimbre(byte patchBank, byte patchId);
private:
struct MidiChannelEntry {
byte currentPatchBank;
byte currentPatchId;
bool usingCustomTimbre;
byte currentCustomTimbreId;
MidiChannelEntry() : currentPatchBank(0),
currentPatchId(0),
usingCustomTimbre(false),
currentCustomTimbreId(0) { }
};
struct MidiCustomTimbreEntry {
bool used;
bool protectionEnabled;
byte currentPatchBank;
byte currentPatchId;
uint32 lastUsedNoteCounter;
MidiCustomTimbreEntry() : used(false),
protectionEnabled(false),
currentPatchBank(0),
currentPatchId(0),
lastUsedNoteCounter(0) {}
};
struct MilesMT32SysExQueueEntry {
uint32 targetAddress;
byte dataPos;
byte data[MILES_CONTROLLER_SYSEX_QUEUE_SIZE + 1]; // 1 extra byte for terminator
MilesMT32SysExQueueEntry() : targetAddress(0),
dataPos(0) {
memset(data, 0, sizeof(data));
}
};
// stores information about all MIDI channels
MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT];
// stores information about all custom timbres
MidiCustomTimbreEntry _customTimbres[MILES_MT32_CUSTOMTIMBRE_COUNT];
byte _patchesBank[MILES_MT32_PATCHES_COUNT];
// holds all instruments
MilesMT32InstrumentEntry *_instrumentTablePtr;
uint16 _instrumentTableCount;
uint32 _noteCounter; // used to figure out, which timbres are outdated
// SysEx Queues
MilesMT32SysExQueueEntry _sysExQueues[MILES_CONTROLLER_SYSEX_QUEUE_COUNT];
};
MidiDriver_Miles_MT32::MidiDriver_Miles_MT32(MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) {
_instrumentTablePtr = instrumentTablePtr;
_instrumentTableCount = instrumentTableCount;
_driver = NULL;
_isOpen = false;
_MT32 = false;
_nativeMT32 = false;
_baseFreq = 250;
_noteCounter = 0;
memset(_patchesBank, 0, sizeof(_patchesBank));
}
MidiDriver_Miles_MT32::~MidiDriver_Miles_MT32() {
Common::StackLock lock(_mutex);
if (_driver) {
_driver->setTimerCallback(0, 0);
_driver->close();
delete _driver;
}
_driver = NULL;
}
int MidiDriver_Miles_MT32::open() {
assert(!_driver);
// Setup midi driver
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32);
MusicType musicType = MidiDriver::getMusicType(dev);
switch (musicType) {
case MT_MT32:
_nativeMT32 = true;
break;
case MT_GM:
if (ConfMan.getBool("native_mt32")) {
_nativeMT32 = true;
}
break;
default:
break;
}
if (!_nativeMT32) {
error("MILES-MT32: non-mt32 currently not supported!");
}
_driver = MidiDriver::createMidi(dev);
if (!_driver)
return 255;
if (_nativeMT32)
_driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
int ret = _driver->open();
if (ret)
return ret;
if (_nativeMT32) {
_driver->sendMT32Reset();
resetMT32();
}
return 0;
}
void MidiDriver_Miles_MT32::close() {
if (_driver) {
_driver->close();
}
}
void MidiDriver_Miles_MT32::resetMT32() {
// reset all internal parameters / patches
MT32SysEx(0x7F0000, milesMT32SysExResetParameters);
// init part/channel assignments
MT32SysEx(0x10000D, milesMT32SysExChansSetup);
// partial reserve table
MT32SysEx(0x100004, milesMT32SysExPartialReserveTable);
// init reverb
MT32SysEx(0x100001, milesMT32SysExInitReverb);
}
void MidiDriver_Miles_MT32::MT32SysEx(const uint32 targetAddress, const byte *dataPtr) {
byte sysExMessage[270];
uint16 sysExPos = 0;
byte sysExByte = 0;
uint16 sysExChecksum = 0;
memset(&sysExMessage, 0, sizeof(sysExMessage));
sysExMessage[0] = 0x41; // Roland
sysExMessage[1] = 0x10;
sysExMessage[2] = 0x16; // Model MT32
sysExMessage[3] = 0x12; // Command DT1
sysExChecksum = 0;
sysExMessage[4] = (targetAddress >> 16) & 0xFF;
sysExMessage[5] = (targetAddress >> 8) & 0xFF;
sysExMessage[6] = targetAddress & 0xFF;
for (byte targetAddressByte = 4; targetAddressByte < 7; targetAddressByte++) {
assert(sysExMessage[targetAddressByte] < 0x80); // security check
sysExChecksum -= sysExMessage[targetAddressByte];
}
sysExPos = 7;
while (1) {
sysExByte = *dataPtr++;
if (sysExByte == MILES_MT32_SYSEX_TERMINATOR)
break; // Message done
assert(sysExPos < sizeof(sysExMessage));
assert(sysExByte < 0x80); // security check
sysExMessage[sysExPos++] = sysExByte;
sysExChecksum -= sysExByte;
}
// Calculate checksum
assert(sysExPos < sizeof(sysExMessage));
sysExMessage[sysExPos++] = sysExChecksum & 0x7f;
// Send SysEx
_driver->sysEx(sysExMessage, sysExPos);
// Wait the time it takes to send the SysEx data
uint32 delay = (sysExPos + 2) * 1000 / 3125;
// Plus an additional delay for the MT-32 rev00
if (_nativeMT32)
delay += 40;
g_system->delayMillis(delay);
}
// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
void MidiDriver_Miles_MT32::send(uint32 b) {
byte command = b & 0xf0;
byte midiChannel = b & 0xf;
byte op1 = (b >> 8) & 0xff;
byte op2 = (b >> 16) & 0xff;
switch (command) {
case 0x80: // note off
case 0x90: // note on
case 0xa0: // Polyphonic key pressure (aftertouch)
case 0xd0: // Channel pressure (aftertouch)
case 0xe0: // pitch bend change
_noteCounter++;
if (_midiChannels[midiChannel].usingCustomTimbre) {
// Remember that this timbre got used now
_customTimbres[_midiChannels[midiChannel].currentCustomTimbreId].lastUsedNoteCounter = _noteCounter;
}
_driver->send(b);
break;
case 0xb0: // Control change
controlChange(midiChannel, op1, op2);
break;
case 0xc0: // Program Change
programChange(midiChannel, op1);
break;
case 0xf0: // SysEx
warning("MILES-MT32: SysEx: %x", b);
break;
default:
warning("MILES-MT32: Unknown event %02x", command);
}
}
void MidiDriver_Miles_MT32::controlChange(byte midiChannel, byte controllerNumber, byte controllerValue) {
byte channelPatchId = 0;
byte channelCustomTimbreId = 0;
switch (controllerNumber) {
case MILES_CONTROLLER_SELECT_PATCH_BANK:
_midiChannels[midiChannel].currentPatchBank = controllerValue;
return;
case MILES_CONTROLLER_PATCH_REVERB:
channelPatchId = _midiChannels[midiChannel].currentPatchId;
writePatchByte(channelPatchId, 6, controllerValue);
_driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change
return;
case MILES_CONTROLLER_PATCH_BENDER:
channelPatchId = _midiChannels[midiChannel].currentPatchId;
writePatchByte(channelPatchId, 4, controllerValue);
_driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change
return;
case MILES_CONTROLLER_REVERB_MODE:
writeToSystemArea(1, controllerValue);
return;
case MILES_CONTROLLER_REVERB_TIME:
writeToSystemArea(2, controllerValue);
return;
case MILES_CONTROLLER_REVERB_LEVEL:
writeToSystemArea(3, controllerValue);
return;
case MILES_CONTROLLER_RHYTHM_KEY_TIMBRE:
if (_midiChannels[midiChannel].usingCustomTimbre) {
// custom timbre is set on current channel
writeRhythmSetup(controllerValue, _midiChannels[midiChannel].currentCustomTimbreId);
}
return;
case MILES_CONTROLLER_PROTECT_TIMBRE:
if (_midiChannels[midiChannel].usingCustomTimbre) {
// custom timbre set on current channel
channelCustomTimbreId = _midiChannels[midiChannel].currentCustomTimbreId;
if (controllerValue >= 64) {
// enable protection
_customTimbres[channelCustomTimbreId].protectionEnabled = true;
} else {
// disable protection
_customTimbres[channelCustomTimbreId].protectionEnabled = false;
}
}
return;
default:
break;
}
if ((controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) {
// send SysEx
byte sysExQueueNr = 0;
// figure out which queue is accessed
controllerNumber -= MILES_CONTROLLER_SYSEX_RANGE_BEGIN;
while (controllerNumber > MILES_CONTROLLER_SYSEX_COMMAND_SEND) {
sysExQueueNr++;
controllerNumber -= (MILES_CONTROLLER_SYSEX_COMMAND_SEND + 1);
}
assert(sysExQueueNr < MILES_CONTROLLER_SYSEX_QUEUE_COUNT);
byte sysExPos = _sysExQueues[sysExQueueNr].dataPos;
bool sysExSend = false;
switch(controllerNumber) {
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1:
_sysExQueues[sysExQueueNr].targetAddress &= 0x00FFFF;
_sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 16);
break;
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2:
_sysExQueues[sysExQueueNr].targetAddress &= 0xFF00FF;
_sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 8);
break;
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3:
_sysExQueues[sysExQueueNr].targetAddress &= 0xFFFF00;
_sysExQueues[sysExQueueNr].targetAddress |= controllerValue;
break;
case MILES_CONTROLLER_SYSEX_COMMAND_DATA:
if (sysExPos < MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
// Space left? put current byte into queue
_sysExQueues[sysExQueueNr].data[sysExPos] = controllerValue;
sysExPos++;
_sysExQueues[sysExQueueNr].dataPos = sysExPos;
if (sysExPos >= MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
// overflow? -> send it now
sysExSend = true;
}
}
break;
case MILES_CONTROLLER_SYSEX_COMMAND_SEND:
sysExSend = true;
break;
default:
assert(0);
}
if (sysExSend) {
if (sysExPos > 0) {
// data actually available? -> send it
_sysExQueues[sysExQueueNr].data[sysExPos] = MILES_MT32_SYSEX_TERMINATOR; // put terminator
// Execute SysEx
MT32SysEx(_sysExQueues[sysExQueueNr].targetAddress, _sysExQueues[sysExQueueNr].data);
// adjust target address to point at the end of the current data
_sysExQueues[sysExQueueNr].targetAddress += sysExPos;
// reset queue data buffer
_sysExQueues[sysExQueueNr].dataPos = 0;
}
}
return;
}
if ((controllerNumber >= MILES_CONTROLLER_XMIDI_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_XMIDI_RANGE_END)) {
// XMIDI controllers? ignore those
return;
}
_driver->send(0xB0 | midiChannel | (controllerNumber << 8) | (controllerValue << 16));
}
void MidiDriver_Miles_MT32::programChange(byte midiChannel, byte patchId) {
byte channelPatchBank = _midiChannels[midiChannel].currentPatchBank;
byte activePatchBank = _patchesBank[patchId];
//warning("patch channel %d, patch %x, bank %x", midiChannel, patchId, channelPatchBank);
// remember patch id for the current MIDI-channel
_midiChannels[midiChannel].currentPatchId = patchId;
if (channelPatchBank != activePatchBank) {
// associate patch with timbre
setupPatch(channelPatchBank, patchId);
}
// If this is a custom patch, remember customTimbreId
int16 customTimbre = searchCustomTimbre(channelPatchBank, patchId);
if (customTimbre >= 0) {
_midiChannels[midiChannel].usingCustomTimbre = true;
_midiChannels[midiChannel].currentCustomTimbreId = customTimbre;
} else {
_midiChannels[midiChannel].usingCustomTimbre = false;
}
// Finally send program change to MT32
_driver->send(0xC0 | midiChannel | (patchId << 8));
}
int16 MidiDriver_Miles_MT32::searchCustomTimbre(byte patchBank, byte patchId) {
byte customTimbreId = 0;
for (customTimbreId = 0; customTimbreId < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreId++) {
if (_customTimbres[customTimbreId].used) {
if ((_customTimbres[customTimbreId].currentPatchBank == patchBank) && (_customTimbres[customTimbreId].currentPatchId == patchId)) {
return customTimbreId;
}
}
}
return -1;
}
const MilesMT32InstrumentEntry *MidiDriver_Miles_MT32::searchCustomInstrument(byte patchBank, byte patchId) {
const MilesMT32InstrumentEntry *instrumentPtr = _instrumentTablePtr;
for (uint16 instrumentNr = 0; instrumentNr < _instrumentTableCount; instrumentNr++) {
if ((instrumentPtr->bankId == patchBank) && (instrumentPtr->patchId == patchId))
return instrumentPtr;
instrumentPtr++;
}
return NULL;
}
void MidiDriver_Miles_MT32::setupPatch(byte patchBank, byte patchId) {
_patchesBank[patchId] = patchBank;
if (patchBank) {
// non-built-in bank
int16 customTimbreId = searchCustomTimbre(patchBank, patchId);
if (customTimbreId >= 0) {
// now available? -> use this timbre
writePatchTimbre(patchId, 2, customTimbreId); // Group MEMORY
return;
}
}
// for built-in bank (or timbres, that are not available) use default MT32 timbres
byte timbreId = patchId & 0x3F;
if (!(patchId & 0x40)) {
writePatchTimbre(patchId, 0, timbreId); // Group A
} else {
writePatchTimbre(patchId, 1, timbreId); // Group B
}
}
void MidiDriver_Miles_MT32::processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize) {
uint16 timbreCount = 0;
uint32 expectedSize = 0;
const byte *timbreListSeeker = timbreListPtr;
if (timbreListSize < 2) {
warning("MILES-MT32: XMIDI-TIMB chunk - not enough bytes in chunk");
return;
}
timbreCount = READ_LE_UINT16(timbreListPtr);
expectedSize = timbreCount * 2;
if (expectedSize > timbreListSize) {
warning("MILES-MT32: XMIDI-TIMB chunk - size mismatch");
return;
}
timbreListSeeker += 2;
while (timbreCount) {
const byte patchId = *timbreListSeeker++;
const byte patchBank = *timbreListSeeker++;
int16 customTimbreId = 0;
switch (patchBank) {
case MILES_MT32_TIMBREBANK_STANDARD_ROLAND:
case MILES_MT32_TIMBREBANK_MELODIC_MODULE:
// ignore those 2 banks
break;
default:
// Check, if this timbre was already loaded
customTimbreId = searchCustomTimbre(patchBank, patchId);
if (customTimbreId < 0) {
// currently not loaded, try to install it
installCustomTimbre(patchBank, patchId);
}
}
timbreCount--;
}
}
//
int16 MidiDriver_Miles_MT32::installCustomTimbre(byte patchBank, byte patchId) {
switch(patchBank) {
case MILES_MT32_TIMBREBANK_STANDARD_ROLAND: // Standard Roland MT32 bank
case MILES_MT32_TIMBREBANK_MELODIC_MODULE: // Reserved for melodic mode
return -1;
default:
break;
}
// Original driver did a search for custom timbre here
// and in case it was found, it would call setup_patch()
// we are called from within setup_patch(), so this isn't needed
int16 customTimbreId = -1;
int16 leastUsedTimbreId = -1;
uint32 leastUsedTimbreNoteCounter = _noteCounter;
const MilesMT32InstrumentEntry *instrumentPtr = NULL;
// Check, if requested instrument is actually available
instrumentPtr = searchCustomInstrument(patchBank, patchId);
if (!instrumentPtr) {
warning("MILES-MT32: instrument not found during installCustomTimbre()");
return -1; // not found -> bail out
}
// Look for an empty timbre slot
// or get the least used non-protected slot
for (byte customTimbreNr = 0; customTimbreNr < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreNr++) {
if (!_customTimbres[customTimbreNr].used) {
// found an empty slot -> use this one
customTimbreId = customTimbreNr;
break;
} else {
// used slot
if (!_customTimbres[customTimbreNr].protectionEnabled) {
// not protected
uint32 customTimbreNoteCounter = _customTimbres[customTimbreNr].lastUsedNoteCounter;
if (customTimbreNoteCounter < leastUsedTimbreNoteCounter) {
leastUsedTimbreId = customTimbreNr;
leastUsedTimbreNoteCounter = customTimbreNoteCounter;
}
}
}
}
if (customTimbreId < 0) {
// no empty slot found, check if we got a least used non-protected slot
if (leastUsedTimbreId < 0) {
// everything is protected, bail out
warning("MILES-MT32: no non-protected timbre slots available during installCustomTimbre()");
return -1;
}
customTimbreId = leastUsedTimbreId;
}
// setup timbre slot
_customTimbres[customTimbreId].used = true;
_customTimbres[customTimbreId].currentPatchBank = patchBank;
_customTimbres[customTimbreId].currentPatchId = patchId;
_customTimbres[customTimbreId].lastUsedNoteCounter = _noteCounter;
_customTimbres[customTimbreId].protectionEnabled = false;
uint32 targetAddress = 0x080000 | (customTimbreId << 9);
uint32 targetAddressCommon = targetAddress + 0x000000;
uint32 targetAddressPartial1 = targetAddress + 0x00000E;
uint32 targetAddressPartial2 = targetAddress + 0x000048;
uint32 targetAddressPartial3 = targetAddress + 0x000102;
uint32 targetAddressPartial4 = targetAddress + 0x00013C;
#if 0
byte parameterData[MILES_MT32_PATCHDATA_TOTAL_SIZE + 1];
uint16 parameterDataPos = 0;
memcpy(parameterData, instrumentPtr->commonParameter, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
parameterDataPos += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[0], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[1], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[2], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[3], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
parameterData[parameterDataPos] = MILES_MT32_SYSEX_TERMINATOR;
MT32SysEx(targetAddressCommon, parameterData);
#endif
// upload common parameter data
MT32SysEx(targetAddressCommon, instrumentPtr->commonParameter);
// upload partial parameter data
MT32SysEx(targetAddressPartial1, instrumentPtr->partialParameters[0]);
MT32SysEx(targetAddressPartial2, instrumentPtr->partialParameters[1]);
MT32SysEx(targetAddressPartial3, instrumentPtr->partialParameters[2]);
MT32SysEx(targetAddressPartial4, instrumentPtr->partialParameters[3]);
setupPatch(patchBank, patchId);
return customTimbreId;
}
uint32 MidiDriver_Miles_MT32::calculateSysExTargetAddress(uint32 baseAddress, uint32 index) {
uint16 targetAddressLSB = baseAddress & 0xFF;
uint16 targetAddressKSB = (baseAddress >> 8) & 0xFF;
uint16 targetAddressMSB = (baseAddress >> 16) & 0xFF;
// add index to it, but use 7-bit of the index for each byte
targetAddressLSB += (index & 0x7F);
targetAddressKSB += ((index >> 7) & 0x7F);
targetAddressMSB += ((index >> 14) & 0x7F);
// adjust bytes, so that none of them is above or equal 0x80
while (targetAddressLSB >= 0x80) {
targetAddressLSB -= 0x80;
targetAddressKSB++;
}
while (targetAddressKSB >= 0x80) {
targetAddressKSB -= 0x80;
targetAddressMSB++;
}
assert(targetAddressMSB < 0x80);
// put everything together
return targetAddressLSB | (targetAddressKSB << 8) | (targetAddressMSB << 16);
}
void MidiDriver_Miles_MT32::writeRhythmSetup(byte note, byte customTimbreId) {
byte sysExData[2];
uint32 targetAddress = 0;
targetAddress = calculateSysExTargetAddress(0x030110, ((note - 24) << 2));
sysExData[0] = customTimbreId;
sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
MT32SysEx(targetAddress, sysExData);
}
void MidiDriver_Miles_MT32::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId) {
byte sysExData[3];
uint32 targetAddress = 0;
// write to patch memory (starts at 0x050000, each entry is 8 bytes)
targetAddress = calculateSysExTargetAddress(0x050000, patchId << 3);
sysExData[0] = timbreGroup; // 0 - group A, 1 - group B, 2 - memory, 3 - rhythm
sysExData[1] = timbreId; // timbre number (0-63)
sysExData[2] = MILES_MT32_SYSEX_TERMINATOR; // terminator
MT32SysEx(targetAddress, sysExData);
}
void MidiDriver_Miles_MT32::writePatchByte(byte patchId, byte index, byte patchValue) {
byte sysExData[2];
uint32 targetAddress = 0;
targetAddress = calculateSysExTargetAddress(0x050000, (patchId << 3) + index);
sysExData[0] = patchValue;
sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
MT32SysEx(targetAddress, sysExData);
}
void MidiDriver_Miles_MT32::writeToSystemArea(byte index, byte value) {
byte sysExData[2];
uint32 targetAddress = 0;
targetAddress = calculateSysExTargetAddress(0x100000, index);
sysExData[0] = value;
sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
MT32SysEx(targetAddress, sysExData);
}
MidiDriver *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename) {
MilesMT32InstrumentEntry *instrumentTablePtr = NULL;
uint16 instrumentTableCount = 0;
if (!instrumentDataFilename.empty()) {
// Load MT32 instrument data from file SAMPLE.MT
Common::File *fileStream = new Common::File();
uint32 fileSize = 0;
byte *fileDataPtr = NULL;
uint32 fileDataOffset = 0;
uint32 fileDataLeft = 0;
byte curBankId = 0;
byte curPatchId = 0;
MilesMT32InstrumentEntry *instrumentPtr = NULL;
uint32 instrumentOffset = 0;
uint16 instrumentDataSize = 0;
if (!fileStream->open(instrumentDataFilename))
error("MILES-MT32: could not open instrument file '%s'", instrumentDataFilename.c_str());
fileSize = fileStream->size();
fileDataPtr = new byte[fileSize];
if (fileStream->read(fileDataPtr, fileSize) != fileSize)
error("MILES-MT32: error while reading instrument file");
fileStream->close();
delete fileStream;
// File is like this:
// [patch:BYTE] [bank:BYTE] [patchoffset:UINT32]
// ...
// until patch + bank are both 0xFF, which signals end of header
// First we check how many entries there are
fileDataOffset = 0;
fileDataLeft = fileSize;
while (1) {
if (fileDataLeft < 6)
error("MILES-MT32: unexpected EOF in instrument file");
curPatchId = fileDataPtr[fileDataOffset++];
curBankId = fileDataPtr[fileDataOffset++];
if ((curBankId == 0xFF) && (curPatchId == 0xFF))
break;
fileDataOffset += 4; // skip over offset
instrumentTableCount++;
}
if (instrumentTableCount == 0)
error("MILES-MT32: no instruments in instrument file");
// Allocate space for instruments
instrumentTablePtr = new MilesMT32InstrumentEntry[instrumentTableCount];
// Now actually read all entries
instrumentPtr = instrumentTablePtr;
fileDataOffset = 0;
fileDataLeft = fileSize;
while (1) {
curPatchId = fileDataPtr[fileDataOffset++];
curBankId = fileDataPtr[fileDataOffset++];
if ((curBankId == 0xFF) && (curPatchId == 0xFF))
break;
instrumentOffset = READ_LE_UINT32(fileDataPtr + fileDataOffset);
fileDataOffset += 4;
instrumentPtr->bankId = curBankId;
instrumentPtr->patchId = curPatchId;
instrumentDataSize = READ_LE_UINT16(fileDataPtr + instrumentOffset);
if (instrumentDataSize != (MILES_MT32_PATCHDATA_TOTAL_SIZE + 2))
error("MILES-MT32: unsupported instrument size");
instrumentOffset += 2;
// Copy common parameter data
memcpy(instrumentPtr->commonParameter, fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
instrumentPtr->commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator
instrumentOffset += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
// Copy partial parameter data
for (byte partialNr = 0; partialNr < MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT; partialNr++) {
memcpy(&instrumentPtr->partialParameters[partialNr], fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
instrumentPtr->partialParameters[partialNr][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator
instrumentOffset += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
}
// Instrument read, next instrument please
instrumentPtr++;
}
// Free instrument file data
delete[] fileDataPtr;
}
return new MidiDriver_Miles_MT32(instrumentTablePtr, instrumentTableCount);
}
void MidiDriver_Miles_MT32_processXMIDITimbreChunk(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize) {
MidiDriver_Miles_MT32 *driverMT32 = dynamic_cast<MidiDriver_Miles_MT32 *>(driver);
if (driverMT32) {
driverMT32->processXMIDITimbreChunk(timbreListPtr, timbreListSize);
}
}
} // End of namespace Audio

View file

@ -219,11 +219,10 @@ void ProtrackerStream::updateRow() {
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;
}
byte trackNote = _module.periodToNote(_track[track].period);
_track[track].arpeggioNotes[0] = trackNote;
_track[track].arpeggioNotes[1] = trackNote + ex;
_track[track].arpeggioNotes[2] = trackNote + ey;
}
break;
case 0x1:

View file

@ -1,6 +1,7 @@
MODULE := audio
MODULE_OBJS := \
adlib.o \
audiostream.o \
fmopl.o \
mididrv.o \
@ -9,11 +10,14 @@ MODULE_OBJS := \
midiparser_xmidi.o \
midiparser.o \
midiplayer.o \
miles_adlib.o \
miles_mt32.o \
mixer.o \
mpu401.o \
musicplugin.o \
null.o \
timestamp.o \
decoders/3do.o \
decoders/aac.o \
decoders/adpcm.o \
decoders/aiff.o \
@ -36,7 +40,6 @@ MODULE_OBJS := \
mods/rjp1.o \
mods/soundfx.o \
mods/tfmx.o \
softsynth/adlib.o \
softsynth/cms.o \
softsynth/opl/dbopl.o \
softsynth/opl/dosbox.o \
@ -55,6 +58,11 @@ MODULE_OBJS := \
softsynth/sid.o \
softsynth/wave6581.o
ifdef USE_ALSA
MODULE_OBJS += \
alsa_opl.o
endif
ifndef USE_ARM_SOUND_ASM
MODULE_OBJS += \
rate.o

View file

@ -0,0 +1,348 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <cstring>
#include "Analog.h"
namespace MT32Emu {
#if MT32EMU_USE_FLOAT_SAMPLES
/* FIR approximation of the overall impulse response of the cascade composed of the sample & hold circuit and the low pass filter
* of the MT-32 first generation.
* The coefficients below are found by windowing the inverse DFT of the 1024 pin frequency response converted to the minimum phase.
* The frequency response of the LPF is computed directly, the effect of the S&H is approximated by multiplying the LPF frequency
* response by the corresponding sinc. Although, the LPF has DC gain of 3.2, we ignore this in the emulation and use normalised model.
* The peak gain of the normalised cascade appears about 1.7 near 11.8 kHz. Relative error doesn't exceed 1% for the frequencies
* below 12.5 kHz. In the higher frequency range, the relative error is below 8%. Peak error value is at 16 kHz.
*/
static const float COARSE_LPF_TAPS_MT32[] = {
1.272473681f, -0.220267785f, -0.158039905f, 0.179603785f, -0.111484097f, 0.054137498f, -0.023518029f, 0.010997169f, -0.006935698f
};
// Similar approximation for new MT-32 and CM-32L/LAPC-I LPF. As the voltage controlled amplifier was introduced, LPF has unity DC gain.
// The peak gain value shifted towards higher frequencies and a bit higher about 1.83 near 13 kHz.
static const float COARSE_LPF_TAPS_CM32L[] = {
1.340615635f, -0.403331694f, 0.036005517f, 0.066156844f, -0.069672532f, 0.049563806f, -0.031113416f, 0.019169774f, -0.012421368f
};
#else
static const unsigned int COARSE_LPF_FRACTION_BITS = 14;
// Integer versions of the FIRs above multiplied by (1 << 14) and rounded.
static const SampleEx COARSE_LPF_TAPS_MT32[] = {
20848, -3609, -2589, 2943, -1827, 887, -385, 180, -114
};
static const SampleEx COARSE_LPF_TAPS_CM32L[] = {
21965, -6608, 590, 1084, -1142, 812, -510, 314, -204
};
#endif
/* Combined FIR that both approximates the impulse response of the analogue circuits of sample & hold and the low pass filter
* in the audible frequency range (below 20 kHz) and attenuates unwanted mirror spectra above 28 kHz as well. It is a polyphase
* filter intended for resampling the signal to 48 kHz yet for applying high frequency boost.
* As with the filter above, the analogue LPF frequency response is obtained for 1536 pin grid for range up to 96 kHz and multiplied
* by the corresponding sinc. The result is further squared, windowed and passed to generalised Parks-McClellan routine as a desired response.
* Finally, the minimum phase factor is found that's essentially the coefficients below.
* Relative error in the audible frequency range doesn't exceed 0.0006%, attenuation in the stopband is better than 100 dB.
* This level of performance makes it nearly bit-accurate for standard 16-bit sample resolution.
*/
// FIR version for MT-32 first generation.
static const float ACCURATE_LPF_TAPS_MT32[] = {
0.003429281f, 0.025929869f, 0.096587777f, 0.228884848f, 0.372413431f, 0.412386503f, 0.263980018f,
-0.014504962f, -0.237394528f, -0.257043496f, -0.103436603f, 0.063996095f, 0.124562333f, 0.083703206f,
0.013921662f, -0.033475018f, -0.046239712f, -0.029310921f, 0.00126585f, 0.021060961f, 0.017925605f,
0.003559874f, -0.005105248f, -0.005647917f, -0.004157918f, -0.002065664f, 0.00158747f, 0.003762585f,
0.001867137f, -0.001090028f, -0.001433979f, -0.00022367f, 4.34308E-05f, -0.000247827f, 0.000157087f,
0.000605823f, 0.000197317f, -0.000370511f, -0.000261202f, 9.96069E-05f, 9.85073E-05f, -5.28754E-05f,
-1.00912E-05f, 7.69943E-05f, 2.03162E-05f, -5.67967E-05f, -3.30637E-05f, 1.61958E-05f, 1.73041E-05f
};
// FIR version for new MT-32 and CM-32L/LAPC-I.
static const float ACCURATE_LPF_TAPS_CM32L[] = {
0.003917452f, 0.030693861f, 0.116424199f, 0.275101674f, 0.43217361f, 0.431247894f, 0.183255659f,
-0.174955671f, -0.354240244f, -0.212401714f, 0.072259178f, 0.204655344f, 0.108336211f, -0.039099027f,
-0.075138174f, -0.026261906f, 0.00582663f, 0.003052193f, 0.00613657f, 0.017017951f, 0.008732535f,
-0.011027427f, -0.012933664f, 0.001158097f, 0.006765958f, 0.00046778f, -0.002191106f, 0.001561017f,
0.001842871f, -0.001996876f, -0.002315836f, 0.000980965f, 0.001817454f, -0.000243272f, -0.000972848f,
0.000149941f, 0.000498886f, -0.000204436f, -0.000347415f, 0.000142386f, 0.000249137f, -4.32946E-05f,
-0.000131231f, 3.88575E-07f, 4.48813E-05f, -1.31906E-06f, -1.03499E-05f, 7.71971E-06f, 2.86721E-06f
};
// According to the CM-64 PCB schematic, there is a difference in the values of the LPF entrance resistors for the reverb and non-reverb channels.
// This effectively results in non-unity LPF DC gain for the reverb channel of 0.68 while the LPF has unity DC gain for the LA32 output channels.
// In emulation, the reverb output gain is multiplied by this factor to compensate for the LPF gain difference.
static const float CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR = 0.68f;
static const unsigned int OUTPUT_GAIN_FRACTION_BITS = 8;
static const float OUTPUT_GAIN_MULTIPLIER = float(1 << OUTPUT_GAIN_FRACTION_BITS);
static const unsigned int COARSE_LPF_DELAY_LINE_LENGTH = 8; // Must be a power of 2
static const unsigned int ACCURATE_LPF_DELAY_LINE_LENGTH = 16; // Must be a power of 2
static const unsigned int ACCURATE_LPF_NUMBER_OF_PHASES = 3; // Upsampling factor
static const unsigned int ACCURATE_LPF_PHASE_INCREMENT_REGULAR = 2; // Downsampling factor
static const unsigned int ACCURATE_LPF_PHASE_INCREMENT_OVERSAMPLED = 1; // No downsampling
static const Bit32u ACCURATE_LPF_DELTAS_REGULAR[][ACCURATE_LPF_NUMBER_OF_PHASES] = { { 0, 0, 0 }, { 1, 1, 0 }, { 1, 2, 1 } };
static const Bit32u ACCURATE_LPF_DELTAS_OVERSAMPLED[][ACCURATE_LPF_NUMBER_OF_PHASES] = { { 0, 0, 0 }, { 1, 0, 0 }, { 1, 0, 1 } };
class AbstractLowPassFilter {
public:
static AbstractLowPassFilter &createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF);
static void muteRingBuffer(SampleEx *ringBuffer, unsigned int length);
virtual ~AbstractLowPassFilter() {}
virtual SampleEx process(SampleEx sample) = 0;
virtual bool hasNextSample() const;
virtual unsigned int getOutputSampleRate() const;
virtual unsigned int estimateInSampleCount(unsigned int outSamples) const;
virtual void addPositionIncrement(unsigned int) {}
};
class NullLowPassFilter : public AbstractLowPassFilter {
public:
SampleEx process(SampleEx sample);
};
class CoarseLowPassFilter : public AbstractLowPassFilter {
private:
const SampleEx * const LPF_TAPS;
SampleEx ringBuffer[COARSE_LPF_DELAY_LINE_LENGTH];
unsigned int ringBufferPosition;
public:
CoarseLowPassFilter(bool oldMT32AnalogLPF);
SampleEx process(SampleEx sample);
};
class AccurateLowPassFilter : public AbstractLowPassFilter {
private:
const float * const LPF_TAPS;
const Bit32u (* const deltas)[ACCURATE_LPF_NUMBER_OF_PHASES];
const unsigned int phaseIncrement;
const unsigned int outputSampleRate;
SampleEx ringBuffer[ACCURATE_LPF_DELAY_LINE_LENGTH];
unsigned int ringBufferPosition;
unsigned int phase;
public:
AccurateLowPassFilter(bool oldMT32AnalogLPF, bool oversample);
SampleEx process(SampleEx sample);
bool hasNextSample() const;
unsigned int getOutputSampleRate() const;
unsigned int estimateInSampleCount(unsigned int outSamples) const;
void addPositionIncrement(unsigned int positionIncrement);
};
Analog::Analog(const AnalogOutputMode mode, const ControlROMFeatureSet *controlROMFeatures) :
leftChannelLPF(AbstractLowPassFilter::createLowPassFilter(mode, controlROMFeatures->isOldMT32AnalogLPF())),
rightChannelLPF(AbstractLowPassFilter::createLowPassFilter(mode, controlROMFeatures->isOldMT32AnalogLPF())),
synthGain(0),
reverbGain(0)
{}
Analog::~Analog() {
delete &leftChannelLPF;
delete &rightChannelLPF;
}
void Analog::process(Sample **outStream, const Sample *nonReverbLeft, const Sample *nonReverbRight, const Sample *reverbDryLeft, const Sample *reverbDryRight, const Sample *reverbWetLeft, const Sample *reverbWetRight, Bit32u outLength) {
if (outStream == NULL) {
leftChannelLPF.addPositionIncrement(outLength);
rightChannelLPF.addPositionIncrement(outLength);
return;
}
while (0 < (outLength--)) {
SampleEx outSampleL;
SampleEx outSampleR;
if (leftChannelLPF.hasNextSample()) {
outSampleL = leftChannelLPF.process(0);
outSampleR = rightChannelLPF.process(0);
} else {
SampleEx inSampleL = ((SampleEx)*(nonReverbLeft++) + (SampleEx)*(reverbDryLeft++)) * synthGain + (SampleEx)*(reverbWetLeft++) * reverbGain;
SampleEx inSampleR = ((SampleEx)*(nonReverbRight++) + (SampleEx)*(reverbDryRight++)) * synthGain + (SampleEx)*(reverbWetRight++) * reverbGain;
#if !MT32EMU_USE_FLOAT_SAMPLES
inSampleL >>= OUTPUT_GAIN_FRACTION_BITS;
inSampleR >>= OUTPUT_GAIN_FRACTION_BITS;
#endif
outSampleL = leftChannelLPF.process(inSampleL);
outSampleR = rightChannelLPF.process(inSampleR);
}
*((*outStream)++) = Synth::clipSampleEx(outSampleL);
*((*outStream)++) = Synth::clipSampleEx(outSampleR);
}
}
unsigned int Analog::getOutputSampleRate() const {
return leftChannelLPF.getOutputSampleRate();
}
Bit32u Analog::getDACStreamsLength(Bit32u outputLength) const {
return leftChannelLPF.estimateInSampleCount(outputLength);
}
void Analog::setSynthOutputGain(float useSynthGain) {
#if MT32EMU_USE_FLOAT_SAMPLES
synthGain = useSynthGain;
#else
if (OUTPUT_GAIN_MULTIPLIER < useSynthGain) useSynthGain = OUTPUT_GAIN_MULTIPLIER;
synthGain = SampleEx(useSynthGain * OUTPUT_GAIN_MULTIPLIER);
#endif
}
void Analog::setReverbOutputGain(float useReverbGain, bool mt32ReverbCompatibilityMode) {
if (!mt32ReverbCompatibilityMode) useReverbGain *= CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR;
#if MT32EMU_USE_FLOAT_SAMPLES
reverbGain = useReverbGain;
#else
if (OUTPUT_GAIN_MULTIPLIER < useReverbGain) useReverbGain = OUTPUT_GAIN_MULTIPLIER;
reverbGain = SampleEx(useReverbGain * OUTPUT_GAIN_MULTIPLIER);
#endif
}
AbstractLowPassFilter &AbstractLowPassFilter::createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF) {
switch (mode) {
case AnalogOutputMode_COARSE:
return *new CoarseLowPassFilter(oldMT32AnalogLPF);
case AnalogOutputMode_ACCURATE:
return *new AccurateLowPassFilter(oldMT32AnalogLPF, false);
case AnalogOutputMode_OVERSAMPLED:
return *new AccurateLowPassFilter(oldMT32AnalogLPF, true);
default:
return *new NullLowPassFilter;
}
}
void AbstractLowPassFilter::muteRingBuffer(SampleEx *ringBuffer, unsigned int length) {
#if MT32EMU_USE_FLOAT_SAMPLES
SampleEx *p = ringBuffer;
while (length--) {
*(p++) = 0.0f;
}
#else
memset(ringBuffer, 0, length * sizeof(SampleEx));
#endif
}
bool AbstractLowPassFilter::hasNextSample() const {
return false;
}
unsigned int AbstractLowPassFilter::getOutputSampleRate() const {
return SAMPLE_RATE;
}
unsigned int AbstractLowPassFilter::estimateInSampleCount(unsigned int outSamples) const {
return outSamples;
}
SampleEx NullLowPassFilter::process(const SampleEx inSample) {
return inSample;
}
CoarseLowPassFilter::CoarseLowPassFilter(bool oldMT32AnalogLPF) :
LPF_TAPS(oldMT32AnalogLPF ? COARSE_LPF_TAPS_MT32 : COARSE_LPF_TAPS_CM32L),
ringBufferPosition(0)
{
muteRingBuffer(ringBuffer, COARSE_LPF_DELAY_LINE_LENGTH);
}
SampleEx CoarseLowPassFilter::process(const SampleEx inSample) {
static const unsigned int DELAY_LINE_MASK = COARSE_LPF_DELAY_LINE_LENGTH - 1;
SampleEx sample = LPF_TAPS[COARSE_LPF_DELAY_LINE_LENGTH] * ringBuffer[ringBufferPosition];
ringBuffer[ringBufferPosition] = Synth::clipSampleEx(inSample);
for (unsigned int i = 0; i < COARSE_LPF_DELAY_LINE_LENGTH; i++) {
sample += LPF_TAPS[i] * ringBuffer[(i + ringBufferPosition) & DELAY_LINE_MASK];
}
ringBufferPosition = (ringBufferPosition - 1) & DELAY_LINE_MASK;
#if !MT32EMU_USE_FLOAT_SAMPLES
sample >>= COARSE_LPF_FRACTION_BITS;
#endif
return sample;
}
AccurateLowPassFilter::AccurateLowPassFilter(const bool oldMT32AnalogLPF, const bool oversample) :
LPF_TAPS(oldMT32AnalogLPF ? ACCURATE_LPF_TAPS_MT32 : ACCURATE_LPF_TAPS_CM32L),
deltas(oversample ? ACCURATE_LPF_DELTAS_OVERSAMPLED : ACCURATE_LPF_DELTAS_REGULAR),
phaseIncrement(oversample ? ACCURATE_LPF_PHASE_INCREMENT_OVERSAMPLED : ACCURATE_LPF_PHASE_INCREMENT_REGULAR),
outputSampleRate(SAMPLE_RATE * ACCURATE_LPF_NUMBER_OF_PHASES / phaseIncrement),
ringBufferPosition(0),
phase(0)
{
muteRingBuffer(ringBuffer, ACCURATE_LPF_DELAY_LINE_LENGTH);
}
SampleEx AccurateLowPassFilter::process(const SampleEx inSample) {
static const unsigned int DELAY_LINE_MASK = ACCURATE_LPF_DELAY_LINE_LENGTH - 1;
float sample = (phase == 0) ? LPF_TAPS[ACCURATE_LPF_DELAY_LINE_LENGTH * ACCURATE_LPF_NUMBER_OF_PHASES] * ringBuffer[ringBufferPosition] : 0.0f;
if (!hasNextSample()) {
ringBuffer[ringBufferPosition] = inSample;
}
for (unsigned int tapIx = phase, delaySampleIx = 0; delaySampleIx < ACCURATE_LPF_DELAY_LINE_LENGTH; delaySampleIx++, tapIx += ACCURATE_LPF_NUMBER_OF_PHASES) {
sample += LPF_TAPS[tapIx] * ringBuffer[(delaySampleIx + ringBufferPosition) & DELAY_LINE_MASK];
}
phase += phaseIncrement;
if (ACCURATE_LPF_NUMBER_OF_PHASES <= phase) {
phase -= ACCURATE_LPF_NUMBER_OF_PHASES;
ringBufferPosition = (ringBufferPosition - 1) & DELAY_LINE_MASK;
}
return SampleEx(ACCURATE_LPF_NUMBER_OF_PHASES * sample);
}
bool AccurateLowPassFilter::hasNextSample() const {
return phaseIncrement <= phase;
}
unsigned int AccurateLowPassFilter::getOutputSampleRate() const {
return outputSampleRate;
}
unsigned int AccurateLowPassFilter::estimateInSampleCount(unsigned int outSamples) const {
Bit32u cycleCount = outSamples / ACCURATE_LPF_NUMBER_OF_PHASES;
Bit32u remainder = outSamples - cycleCount * ACCURATE_LPF_NUMBER_OF_PHASES;
return cycleCount * phaseIncrement + deltas[remainder][phase];
}
void AccurateLowPassFilter::addPositionIncrement(const unsigned int positionIncrement) {
phase = (phase + positionIncrement * phaseIncrement) % ACCURATE_LPF_NUMBER_OF_PHASES;
}
}

View file

@ -0,0 +1,57 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_ANALOG_H
#define MT32EMU_ANALOG_H
#include "mt32emu.h"
namespace MT32Emu {
class AbstractLowPassFilter;
/* Analog class is dedicated to perform fair emulation of analogue circuitry of hardware units that is responsible
* for processing output signal after the DAC. It appears that the analogue circuit labeled "LPF" on the schematic
* also applies audible changes to the signal spectra. There is a significant boost of higher frequencies observed
* aside from quite poor attenuation of the mirror spectra above 16 kHz which is due to a relatively low filter order.
*
* As the final mixing of multiplexed output signal is performed after the DAC, this function is migrated here from Synth.
* Saying precisely, mixing is performed within the LPF as the entrance resistors are actually components of a LPF
* designed using the multiple feedback topology. Nevertheless, the schematic separates them.
*/
class Analog {
public:
Analog(AnalogOutputMode mode, const ControlROMFeatureSet *controlROMFeatures);
~Analog();
void process(Sample **outStream, const Sample *nonReverbLeft, const Sample *nonReverbRight, const Sample *reverbDryLeft, const Sample *reverbDryRight, const Sample *reverbWetLeft, const Sample *reverbWetRight, const Bit32u outLength);
unsigned int getOutputSampleRate() const;
Bit32u getDACStreamsLength(Bit32u outputLength) const;
void setSynthOutputGain(float synthGain);
void setReverbOutputGain(float reverbGain, bool mt32ReverbCompatibilityMode);
private:
AbstractLowPassFilter &leftChannelLPF;
AbstractLowPassFilter &rightChannelLPF;
SampleEx synthGain;
SampleEx reverbGain;
Analog(Analog &);
};
}
#endif

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <memory.h>
//#include <cstring>
#include "mt32emu.h"
#include "BReverbModel.h"
@ -501,9 +501,9 @@ void BReverbModel::process(const Sample *inLeft, const Sample *inRight, Sample *
* Analysing of the algorithm suggests that the overflow is most probable when the combs output is added below.
* So, despite this isn't actually accurate, we only add the check here for performance reasons.
*/
Sample outSample = Synth::clipBit16s(Synth::clipBit16s(Synth::clipBit16s(Synth::clipBit16s((Bit32s)outL1 + Bit32s(outL1 >> 1)) + (Bit32s)outL2) + Bit32s(outL2 >> 1)) + (Bit32s)outL3);
Sample outSample = Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx((SampleEx)outL1 + SampleEx(outL1 >> 1)) + (SampleEx)outL2) + SampleEx(outL2 >> 1)) + (SampleEx)outL3);
#else
Sample outSample = Synth::clipBit16s((Bit32s)outL1 + Bit32s(outL1 >> 1) + (Bit32s)outL2 + Bit32s(outL2 >> 1) + (Bit32s)outL3);
Sample outSample = Synth::clipSampleEx((SampleEx)outL1 + SampleEx(outL1 >> 1) + (SampleEx)outL2 + SampleEx(outL2 >> 1) + (SampleEx)outL3);
#endif
*(outLeft++) = weirdMul(outSample, wetLevel, 0xFF);
}
@ -515,9 +515,9 @@ void BReverbModel::process(const Sample *inLeft, const Sample *inRight, Sample *
Sample outSample = 1.5f * (outR1 + outR2) + outR3;
#elif MT32EMU_BOSS_REVERB_PRECISE_MODE
// See the note above for the left channel output.
Sample outSample = Synth::clipBit16s(Synth::clipBit16s(Synth::clipBit16s(Synth::clipBit16s((Bit32s)outR1 + Bit32s(outR1 >> 1)) + (Bit32s)outR2) + Bit32s(outR2 >> 1)) + (Bit32s)outR3);
Sample outSample = Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx((SampleEx)outR1 + SampleEx(outR1 >> 1)) + (SampleEx)outR2) + SampleEx(outR2 >> 1)) + (SampleEx)outR3);
#else
Sample outSample = Synth::clipBit16s((Bit32s)outR1 + Bit32s(outR1 >> 1) + (Bit32s)outR2 + Bit32s(outR2 >> 1) + (Bit32s)outR3);
Sample outSample = Synth::clipSampleEx((SampleEx)outR1 + SampleEx(outR1 >> 1) + (SampleEx)outR2 + SampleEx(outR2 >> 1) + (SampleEx)outR3);
#endif
*(outRight++) = weirdMul(outSample, wetLevel, 0xFF);
}

View file

@ -95,7 +95,6 @@ class BReverbModel {
const bool tapDelayMode;
Bit32u dryAmp;
Bit32u wetLevel;
void mute();
static const BReverbSettings &getCM32L_LAPCSettings(const ReverbMode mode);
static const BReverbSettings &getMT32Settings(const ReverbMode mode);
@ -107,6 +106,7 @@ public:
void open();
// May be called multiple times without an open() in between.
void close();
void mute();
void setParameters(Bit8u time, Bit8u level);
void process(const Sample *inLeft, const Sample *inRight, Sample *outLeft, Sample *outRight, unsigned long numSamples);
bool isActive() const;

View file

@ -18,7 +18,7 @@
//#include <cmath>
#include "mt32emu.h"
#include "mmath.h"
#include "LA32FloatWaveGenerator.h"
#include "internals.h"
namespace MT32Emu {

View file

@ -50,8 +50,8 @@ We haven't fully explored:
//#include <cmath>
#include "mt32emu.h"
#include "LA32Ramp.h"
#include "mmath.h"
#include "internals.h"
namespace MT32Emu {

View file

@ -15,15 +15,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <cmath>
#include "mt32emu.h"
#include "mmath.h"
#include "LA32WaveGenerator.h"
#if MT32EMU_USE_FLOAT_SAMPLES
#include "LA32FloatWaveGenerator.cpp"
#else
//#include <cmath>
#include "mt32emu.h"
#include "mmath.h"
#include "internals.h"
namespace MT32Emu {
static const Bit32u SINE_SEGMENT_RELATIVE_LENGTH = 1 << 18;

View file

@ -0,0 +1,124 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_MEMORY_REGION_H
#define MT32EMU_MEMORY_REGION_H
namespace MT32Emu {
enum MemoryRegionType {
MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset
};
class MemoryRegion {
private:
Synth *synth;
Bit8u *realMemory;
Bit8u *maxTable;
public:
MemoryRegionType type;
Bit32u startAddr, entrySize, entries;
MemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable, MemoryRegionType useType, Bit32u useStartAddr, Bit32u useEntrySize, Bit32u useEntries) {
synth = useSynth;
realMemory = useRealMemory;
maxTable = useMaxTable;
type = useType;
startAddr = useStartAddr;
entrySize = useEntrySize;
entries = useEntries;
}
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;
}
Bit8u getMaxValue(int off) const {
if (maxTable == NULL)
return 0xFF;
return maxTable[off % entrySize];
}
Bit8u *getRealMemory() const {
return realMemory;
}
bool isReadable() const {
return getRealMemory() != NULL;
}
void read(unsigned int entry, unsigned int off, Bit8u *dst, unsigned int len) const;
void write(unsigned int entry, unsigned int off, const Bit8u *src, unsigned int len, bool init = false) const;
};
class PatchTempMemoryRegion : public MemoryRegion {
public:
PatchTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9) {}
};
class RhythmTempMemoryRegion : public MemoryRegion {
public:
RhythmTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85) {}
};
class TimbreTempMemoryRegion : public MemoryRegion {
public:
TimbreTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8) {}
};
class PatchesMemoryRegion : public MemoryRegion {
public:
PatchesMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128) {}
};
class TimbresMemoryRegion : public MemoryRegion {
public:
TimbresMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64) {}
};
class SystemMemoryRegion : public MemoryRegion {
public:
SystemMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::System), 1) {}
};
class DisplayMemoryRegion : public MemoryRegion {
public:
DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1) {}
};
class ResetMemoryRegion : public MemoryRegion {
public:
ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {}
};
}
#endif

View file

@ -0,0 +1,67 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_MIDI_EVENT_QUEUE_H
#define MT32EMU_MIDI_EVENT_QUEUE_H
namespace MT32Emu {
/**
* Used to safely store timestamped MIDI events in a local queue.
*/
struct MidiEvent {
Bit32u shortMessageData;
const Bit8u *sysexData;
Bit32u sysexLength;
Bit32u timestamp;
~MidiEvent();
void setShortMessage(Bit32u shortMessageData, Bit32u timestamp);
void setSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp);
};
/**
* Simple queue implementation using a ring buffer to store incoming MIDI event before the synth actually processes it.
* It is intended to:
* - get rid of prerenderer while retaining graceful partial abortion
* - add fair emulation of the MIDI interface delays
* - extend the synth interface with the default implementation of a typical rendering loop.
* THREAD SAFETY:
* It is safe to use either in a single thread environment or when there are only two threads - one performs only reading
* and one performs only writing. More complicated usage requires external synchronisation.
*/
class MidiEventQueue {
private:
MidiEvent * const ringBuffer;
const Bit32u ringBufferMask;
volatile Bit32u startPosition;
volatile Bit32u endPosition;
public:
MidiEventQueue(Bit32u ringBufferSize = DEFAULT_MIDI_EVENT_QUEUE_SIZE); // Must be a power of 2
~MidiEventQueue();
void reset();
bool pushShortMessage(Bit32u shortMessageData, Bit32u timestamp);
bool pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp);
const MidiEvent *peekMidiEvent();
void dropMidiEvent();
bool isFull() const;
};
}
#endif

View file

@ -19,6 +19,7 @@
//#include <cstring>
#include "mt32emu.h"
#include "internals.h"
#include "PartialManager.h"
namespace MT32Emu {

View file

@ -21,6 +21,7 @@
#include "mt32emu.h"
#include "mmath.h"
#include "internals.h"
namespace MT32Emu {
@ -312,8 +313,8 @@ bool Partial::produceOutput(Sample *leftBuf, Sample *rightBuf, unsigned long len
// Though, it is unknown whether this overflow is exploited somewhere.
Sample leftOut = Sample((sample * leftPanValue) >> 8);
Sample rightOut = Sample((sample * rightPanValue) >> 8);
*leftBuf = Synth::clipBit16s((Bit32s)*leftBuf + (Bit32s)leftOut);
*rightBuf = Synth::clipBit16s((Bit32s)*rightBuf + (Bit32s)rightOut);
*leftBuf = Synth::clipSampleEx((SampleEx)*leftBuf + (SampleEx)leftOut);
*rightBuf = Synth::clipSampleEx((SampleEx)*rightBuf + (SampleEx)rightOut);
leftBuf++;
rightBuf++;
#endif

View file

@ -18,6 +18,7 @@
//#include <cstring>
#include "mt32emu.h"
#include "internals.h"
#include "PartialManager.h"
namespace MT32Emu {

View file

@ -16,6 +16,7 @@
*/
#include "mt32emu.h"
#include "internals.h"
namespace MT32Emu {

View file

@ -21,6 +21,7 @@
namespace MT32Emu {
class Part;
class Partial;
enum PolyState {
POLY_Playing,

View file

@ -21,8 +21,8 @@
namespace MT32Emu {
static const ROMInfo *getKnownROMInfoFromList(unsigned int index) {
static const ControlROMFeatureSet MT32_COMPATIBLE(true);
static const ControlROMFeatureSet CM32L_COMPATIBLE(false);
static const ControlROMFeatureSet MT32_COMPATIBLE(true, true);
static const ControlROMFeatureSet CM32L_COMPATIBLE(false, false);
// Known ROMs
static const ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, NULL, &MT32_COMPATIBLE};
@ -106,7 +106,6 @@ void ROMImage::freeROMImage(const ROMImage *romImage) {
delete romImage;
}
Common::File* ROMImage::getFile() const {
return file;
}
@ -115,11 +114,17 @@ const ROMInfo* ROMImage::getROMInfo() const {
return romInfo;
}
ControlROMFeatureSet::ControlROMFeatureSet(bool useDefaultReverbMT32Compatible) : defaultReverbMT32Compatible(useDefaultReverbMT32Compatible) {
}
ControlROMFeatureSet::ControlROMFeatureSet(bool useDefaultReverbMT32Compatible, bool useOldMT32AnalogLPF) :
defaultReverbMT32Compatible(useDefaultReverbMT32Compatible),
oldMT32AnalogLPF(useOldMT32AnalogLPF)
{}
bool ControlROMFeatureSet::isDefaultReverbMT32Compatible() const {
return defaultReverbMT32Compatible;
}
bool ControlROMFeatureSet::isOldMT32AnalogLPF() const {
return oldMT32AnalogLPF;
}
}

View file

@ -77,10 +77,12 @@ public:
struct ControlROMFeatureSet {
private:
unsigned int defaultReverbMT32Compatible : 1;
unsigned int oldMT32AnalogLPF : 1;
public:
ControlROMFeatureSet(bool defaultReverbMT32Compatible);
ControlROMFeatureSet(bool defaultReverbMT32Compatible, bool oldMT32AnalogLPF);
bool isDefaultReverbMT32Compatible() const;
bool isOldMT32AnalogLPF() const;
};
}

View file

@ -31,19 +31,6 @@ namespace MT32Emu {
#define MT32EMU_ALIGN_PACKED __attribute__((packed))
#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;
#if MT32EMU_USE_FLOAT_SAMPLES
typedef float Sample;
#else
typedef Bit16s Sample;
#endif
// 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
@ -184,7 +171,37 @@ struct MemParams {
#pragma pack()
#endif
struct ControlROMPCMStruct;
struct ControlROMMap {
Bit16u idPos;
Bit16u idLen;
const char *idBytes;
Bit16u pcmTable; // 4 * pcmCount bytes
Bit16u pcmCount;
Bit16u timbreAMap; // 128 bytes
Bit16u timbreAOffset;
bool timbreACompressed;
Bit16u timbreBMap; // 128 bytes
Bit16u timbreBOffset;
bool timbreBCompressed;
Bit16u timbreRMap; // 2 * timbreRCount bytes
Bit16u timbreRCount;
Bit16u rhythmSettings; // 4 * rhythmSettingsCount bytes
Bit16u rhythmSettingsCount;
Bit16u reserveSettings; // 9 bytes
Bit16u panSettings; // 8 bytes
Bit16u programSettings; // 8 bytes
Bit16u rhythmMaxTable; // 4 bytes
Bit16u patchMaxTable; // 16 bytes
Bit16u systemMaxTable; // 23 bytes
Bit16u timbreMaxTable; // 72 bytes
};
struct ControlROMPCMStruct {
Bit8u pos;
Bit8u len;
Bit8u pitchLSB;
Bit8u pitchMSB;
};
struct PCMWaveEntry {
Bit32u addr;
@ -216,8 +233,6 @@ struct PatchCache {
const TimbreParam::PartialParam *partialParam;
};
class Partial; // Forward reference for class defined in partial.h
}
#endif

View file

@ -22,12 +22,19 @@
#include "mt32emu.h"
#include "mmath.h"
#include "PartialManager.h"
#include "internals.h"
#include "Analog.h"
#include "BReverbModel.h"
#include "common/debug.h"
#include "MemoryRegion.h"
#include "MidiEventQueue.h"
#include "PartialManager.h"
namespace MT32Emu {
// MIDI interface data transfer rate in samples. Used to simulate the transfer delay.
static const double MIDI_DATA_TRANSFER_RATE = (double)SAMPLE_RATE / 31250.0 * 8.0;
static const ControlROMMap ControlROMMaps[7] = {
// ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrAC tmbrB tmbrBO, tmbrBC tmbrR trC rhythm rhyC rsrv panpot prog rhyMax patMax sysMax timMax
{0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A},
@ -46,18 +53,15 @@ static inline void advanceStreamPosition(Sample *&stream, Bit32u posDelta) {
}
}
Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) {
Bit8u Synth::calcSysexChecksum(const Bit8u *data, const Bit32u len, const Bit8u initChecksum) {
unsigned int checksum = -initChecksum;
for (unsigned int i = 0; i < len; i++) {
checksum = checksum + data[i];
checksum -= data[i];
}
checksum = checksum & 0x7f;
if (checksum) {
checksum = 0x80 - checksum;
}
return checksum;
return Bit8u(checksum & 0x7f);
}
Synth::Synth(ReportHandler *useReportHandler) {
Synth::Synth(ReportHandler *useReportHandler) : mt32ram(*new MemParams()), mt32default(*new MemParams()) {
isOpen = false;
reverbOverridden = false;
partialCount = DEFAULT_MAX_PARTIALS;
@ -75,6 +79,7 @@ Synth::Synth(ReportHandler *useReportHandler) {
reverbModels[i] = NULL;
}
reverbModel = NULL;
analog = NULL;
setDACInputMode(DACInputMode_NICE);
setMIDIDelayMode(MIDIDelayMode_DELAY_SHORT_MESSAGES_ONLY);
setOutputGain(1.0f);
@ -92,6 +97,8 @@ Synth::~Synth() {
if (isDefaultReportHandler) {
delete reportHandler;
}
delete &mt32ram;
delete &mt32default;
}
void ReportHandler::showLCDMessage(const char *data) {
@ -126,7 +133,7 @@ void Synth::printDebug(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
#if MT32EMU_DEBUG_SAMPLESTAMPS > 0
reportHandler->printDebug("[%u] ", renderedSampleCount);
reportHandler->printDebug("[%u] ", (char *)&renderedSampleCount);
#endif
reportHandler->printDebug(fmt, ap);
va_end(ap);
@ -211,10 +218,7 @@ MIDIDelayMode Synth::getMIDIDelayMode() const {
void Synth::setOutputGain(float newOutputGain) {
if (newOutputGain < 0.0f) newOutputGain = -newOutputGain;
outputGain = newOutputGain;
#if !MT32EMU_USE_FLOAT_SAMPLES
if (256.0f < newOutputGain) newOutputGain = 256.0f;
effectiveOutputGain = int(newOutputGain * 256.0f);
#endif
if (analog != NULL) analog->setSynthOutputGain(newOutputGain);
}
float Synth::getOutputGain() const {
@ -224,13 +228,7 @@ float Synth::getOutputGain() const {
void Synth::setReverbOutputGain(float newReverbOutputGain) {
if (newReverbOutputGain < 0.0f) newReverbOutputGain = -newReverbOutputGain;
reverbOutputGain = newReverbOutputGain;
if (!isMT32ReverbCompatibilityMode()) newReverbOutputGain *= CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR;
#if MT32EMU_USE_FLOAT_SAMPLES
effectiveReverbOutputGain = newReverbOutputGain;
#else
if (256.0f < newReverbOutputGain) newReverbOutputGain = 256.0f;
effectiveReverbOutputGain = int(newReverbOutputGain * 256.0f);
#endif
if (analog != NULL) analog->setReverbOutputGain(newReverbOutputGain, isMT32ReverbCompatibilityMode());
}
float Synth::getReverbOutputGain() const {
@ -393,7 +391,11 @@ bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int count, int startTi
return true;
}
bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount) {
bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, AnalogOutputMode analogOutputMode) {
return open(controlROMImage, pcmROMImage, DEFAULT_MAX_PARTIALS, analogOutputMode);
}
bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount, AnalogOutputMode analogOutputMode) {
if (isOpen) {
return false;
}
@ -548,6 +550,10 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, u
midiQueue = new MidiEventQueue();
analog = new Analog(analogOutputMode, controlROMFeatures);
setOutputGain(outputGain);
setReverbOutputGain(reverbOutputGain);
isOpen = true;
isEnabled = false;
@ -565,6 +571,9 @@ void Synth::close(bool forced) {
delete midiQueue;
midiQueue = NULL;
delete analog;
analog = NULL;
delete partialManager;
partialManager = NULL;
@ -603,16 +612,37 @@ void Synth::flushMIDIQueue() {
}
}
void Synth::setMIDIEventQueueSize(Bit32u useSize) {
if (midiQueue != NULL) {
flushMIDIQueue();
delete midiQueue;
midiQueue = new MidiEventQueue(useSize);
Bit32u Synth::setMIDIEventQueueSize(Bit32u useSize) {
static const Bit32u MAX_QUEUE_SIZE = (1 << 24); // This results in about 256 Mb - much greater than any reasonable value
if (midiQueue == NULL) return 0;
flushMIDIQueue();
// Find a power of 2 that is >= useSize
Bit32u binarySize = 1;
if (useSize < MAX_QUEUE_SIZE) {
// Using simple linear search as this isn't time critical
while (binarySize < useSize) binarySize <<= 1;
} else {
binarySize = MAX_QUEUE_SIZE;
}
delete midiQueue;
midiQueue = new MidiEventQueue(binarySize);
return binarySize;
}
Bit32u Synth::getShortMessageLength(Bit32u msg) {
if ((msg & 0xF0) == 0xF0) return 1;
if ((msg & 0xF0) == 0xF0) {
switch (msg & 0xFF) {
case 0xF1:
case 0xF3:
return 2;
case 0xF2:
return 3;
default:
return 1;
}
}
// NOTE: This calculation isn't quite correct
// as it doesn't consider the running status byte
return ((msg & 0xE0) == 0xC0) ? 2 : 3;
@ -638,6 +668,7 @@ bool Synth::playMsg(Bit32u msg, Bit32u timestamp) {
if (midiDelayMode != MIDIDelayMode_IMMEDIATE) {
timestamp = addMIDIInterfaceDelay(getShortMessageLength(msg), timestamp);
}
if (!isEnabled) isEnabled = true;
return midiQueue->pushShortMessage(msg, timestamp);
}
@ -650,16 +681,19 @@ bool Synth::playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp) {
if (midiDelayMode == MIDIDelayMode_DELAY_ALL) {
timestamp = addMIDIInterfaceDelay(len, timestamp);
}
if (!isEnabled) isEnabled = true;
return midiQueue->pushSysex(sysex, len, timestamp);
}
void Synth::playMsgNow(Bit32u msg) {
// FIXME: Implement active sensing
// NOTE: Active sense IS implemented in real hardware. However, realtime processing is clearly out of the library scope.
// It is assumed that realtime consumers of the library respond to these MIDI events as appropriate.
unsigned char code = (unsigned char)((msg & 0x0000F0) >> 4);
unsigned char chan = (unsigned char)(msg & 0x00000F);
unsigned char note = (unsigned char)((msg & 0x007F00) >> 8);
unsigned char velocity = (unsigned char)((msg & 0x7F0000) >> 16);
isEnabled = true;
if (!isEnabled) isEnabled = true;
//printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note);
@ -831,7 +865,7 @@ void Synth::playSysexWithoutHeader(unsigned char device, unsigned char command,
printDebug("playSysexWithoutHeader: Message is too short (%d bytes)!", len);
return;
}
unsigned char checksum = calcSysexChecksum(sysex, len - 1, 0);
Bit8u checksum = calcSysexChecksum(sysex, len - 1);
if (checksum != sysex[len - 1]) {
printDebug("playSysexWithoutHeader: Message checksum is incorrect (provided: %02x, expected: %02x)!", sysex[len - 1], checksum);
return;
@ -1410,9 +1444,8 @@ void MidiEvent::setSysex(const Bit8u *useSysexData, Bit32u useSysexLength, Bit32
memcpy(dstSysexData, useSysexData, sysexLength);
}
MidiEventQueue::MidiEventQueue(Bit32u useRingBufferSize) : ringBufferSize(useRingBufferSize) {
ringBuffer = new MidiEvent[ringBufferSize];
memset(ringBuffer, 0, ringBufferSize * sizeof(MidiEvent));
MidiEventQueue::MidiEventQueue(Bit32u useRingBufferSize) : ringBuffer(new MidiEvent[useRingBufferSize]), ringBufferMask(useRingBufferSize - 1) {
memset(ringBuffer, 0, useRingBufferSize * sizeof(MidiEvent));
reset();
}
@ -1426,7 +1459,7 @@ void MidiEventQueue::reset() {
}
bool MidiEventQueue::pushShortMessage(Bit32u shortMessageData, Bit32u timestamp) {
unsigned int newEndPosition = (endPosition + 1) % ringBufferSize;
Bit32u newEndPosition = (endPosition + 1) & ringBufferMask;
// Is ring buffer full?
if (startPosition == newEndPosition) return false;
ringBuffer[endPosition].setShortMessage(shortMessageData, timestamp);
@ -1435,7 +1468,7 @@ bool MidiEventQueue::pushShortMessage(Bit32u shortMessageData, Bit32u timestamp)
}
bool MidiEventQueue::pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp) {
unsigned int newEndPosition = (endPosition + 1) % ringBufferSize;
Bit32u newEndPosition = (endPosition + 1) & ringBufferMask;
// Is ring buffer full?
if (startPosition == newEndPosition) return false;
ringBuffer[endPosition].setSysex(sysexData, sysexLength, timestamp);
@ -1450,31 +1483,36 @@ const MidiEvent *MidiEventQueue::peekMidiEvent() {
void MidiEventQueue::dropMidiEvent() {
// Is ring buffer empty?
if (startPosition != endPosition) {
startPosition = (startPosition + 1) % ringBufferSize;
startPosition = (startPosition + 1) & ringBufferMask;
}
}
bool MidiEventQueue::isFull() const {
return startPosition == ((endPosition + 1) & ringBufferMask);
}
unsigned int Synth::getStereoOutputSampleRate() const {
return (analog == NULL) ? SAMPLE_RATE : analog->getOutputSampleRate();
}
void Synth::render(Sample *stream, Bit32u len) {
Sample tmpNonReverbLeft[MAX_SAMPLES_PER_RUN];
Sample tmpNonReverbRight[MAX_SAMPLES_PER_RUN];
Sample tmpReverbDryLeft[MAX_SAMPLES_PER_RUN];
Sample tmpReverbDryRight[MAX_SAMPLES_PER_RUN];
Sample tmpReverbWetLeft[MAX_SAMPLES_PER_RUN];
Sample tmpReverbWetRight[MAX_SAMPLES_PER_RUN];
if (!isEnabled) {
renderedSampleCount += analog->getDACStreamsLength(len);
analog->process(NULL, NULL, NULL, NULL, NULL, NULL, NULL, len);
muteSampleBuffer(stream, len << 1);
return;
}
// As in AnalogOutputMode_ACCURATE mode output is upsampled, buffer size MAX_SAMPLES_PER_RUN is more than enough.
Sample tmpNonReverbLeft[MAX_SAMPLES_PER_RUN], tmpNonReverbRight[MAX_SAMPLES_PER_RUN];
Sample tmpReverbDryLeft[MAX_SAMPLES_PER_RUN], tmpReverbDryRight[MAX_SAMPLES_PER_RUN];
Sample tmpReverbWetLeft[MAX_SAMPLES_PER_RUN], tmpReverbWetRight[MAX_SAMPLES_PER_RUN];
while (len > 0) {
Bit32u thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len;
renderStreams(tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisLen);
for (Bit32u i = 0; i < thisLen; i++) {
#if MT32EMU_USE_FLOAT_SAMPLES
*(stream++) = tmpNonReverbLeft[i] + tmpReverbDryLeft[i] + tmpReverbWetLeft[i];
*(stream++) = tmpNonReverbRight[i] + tmpReverbDryRight[i] + tmpReverbWetRight[i];
#else
*(stream++) = clipBit16s((Bit32s)tmpNonReverbLeft[i] + (Bit32s)tmpReverbDryLeft[i] + (Bit32s)tmpReverbWetLeft[i]);
*(stream++) = clipBit16s((Bit32s)tmpNonReverbRight[i] + (Bit32s)tmpReverbDryRight[i] + (Bit32s)tmpReverbWetRight[i]);
#endif
}
len -= thisLen;
Bit32u thisPassLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len;
renderStreams(tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, analog->getDACStreamsLength(thisPassLen));
analog->process(&stream, tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisPassLen);
len -= thisPassLen;
}
}
@ -1518,7 +1556,10 @@ void Synth::renderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample
// In GENERATION2 units, the output from LA32 goes to the Boss chip already bit-shifted.
// In NICE mode, it's also better to increase volume before the reverb processing to preserve accuracy.
void Synth::produceLA32Output(Sample *buffer, Bit32u len) {
#if !MT32EMU_USE_FLOAT_SAMPLES
#if MT32EMU_USE_FLOAT_SAMPLES
(void)buffer;
(void)len;
#else
switch (dacInputMode) {
case DACInputMode_GENERATION2:
while (len--) {
@ -1528,7 +1569,7 @@ void Synth::produceLA32Output(Sample *buffer, Bit32u len) {
break;
case DACInputMode_NICE:
while (len--) {
*buffer = clipBit16s(Bit32s(*buffer) << 1);
*buffer = clipSampleEx(SampleEx(*buffer) << 1);
++buffer;
}
break;
@ -1538,26 +1579,16 @@ void Synth::produceLA32Output(Sample *buffer, Bit32u len) {
#endif
}
void Synth::convertSamplesToOutput(Sample *buffer, Bit32u len, bool reverb) {
if (dacInputMode == DACInputMode_PURE) return;
void Synth::convertSamplesToOutput(Sample *buffer, Bit32u len) {
#if MT32EMU_USE_FLOAT_SAMPLES
float gain = reverb ? effectiveReverbOutputGain : outputGain;
while (len--) {
*(buffer++) *= gain;
}
(void)buffer;
(void)len;
#else
int gain = reverb ? effectiveReverbOutputGain : effectiveOutputGain;
if (dacInputMode == DACInputMode_GENERATION1) {
while (len--) {
Bit32s target = Bit16s((*buffer & 0x8000) | ((*buffer << 1) & 0x7FFE));
*(buffer++) = clipBit16s((target * gain) >> 8);
*buffer = Sample((*buffer & 0x8000) | ((*buffer << 1) & 0x7FFE));
++buffer;
}
return;
}
while (len--) {
*buffer = clipBit16s((Bit32s(*buffer) * gain) >> 8);
++buffer;
}
#endif
}
@ -1566,18 +1597,18 @@ void Synth::doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sampl
// Even if LA32 output isn't desired, we proceed anyway with temp buffers
Sample tmpBufNonReverbLeft[MAX_SAMPLES_PER_RUN], tmpBufNonReverbRight[MAX_SAMPLES_PER_RUN];
if (nonReverbLeft == NULL) nonReverbLeft = tmpBufNonReverbLeft;
if (nonReverbLeft == NULL) nonReverbRight = tmpBufNonReverbRight;
if (nonReverbRight == NULL) nonReverbRight = tmpBufNonReverbRight;
Sample tmpBufReverbDryLeft[MAX_SAMPLES_PER_RUN], tmpBufReverbDryRight[MAX_SAMPLES_PER_RUN];
if (reverbDryLeft == NULL) reverbDryLeft = tmpBufReverbDryLeft;
if (reverbDryRight == NULL) reverbDryRight = tmpBufReverbDryRight;
muteSampleBuffer(nonReverbLeft, len);
muteSampleBuffer(nonReverbRight, len);
muteSampleBuffer(reverbDryLeft, len);
muteSampleBuffer(reverbDryRight, len);
if (isEnabled) {
muteSampleBuffer(nonReverbLeft, len);
muteSampleBuffer(nonReverbRight, len);
muteSampleBuffer(reverbDryLeft, len);
muteSampleBuffer(reverbDryRight, len);
for (unsigned int i = 0; i < getPartialCount(); i++) {
if (partialManager->shouldReverb(i)) {
partialManager->produceOutput(i, reverbDryLeft, reverbDryRight, len);
@ -1591,8 +1622,8 @@ void Synth::doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sampl
if (isReverbEnabled()) {
reverbModel->process(reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, len);
if (reverbWetLeft != NULL) convertSamplesToOutput(reverbWetLeft, len, true);
if (reverbWetRight != NULL) convertSamplesToOutput(reverbWetRight, len, true);
if (reverbWetLeft != NULL) convertSamplesToOutput(reverbWetLeft, len);
if (reverbWetRight != NULL) convertSamplesToOutput(reverbWetRight, len);
} else {
muteSampleBuffer(reverbWetLeft, len);
muteSampleBuffer(reverbWetRight, len);
@ -1601,15 +1632,20 @@ void Synth::doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sampl
// Don't bother with conversion if the output is going to be unused
if (nonReverbLeft != tmpBufNonReverbLeft) {
produceLA32Output(nonReverbLeft, len);
convertSamplesToOutput(nonReverbLeft, len, false);
convertSamplesToOutput(nonReverbLeft, len);
}
if (nonReverbRight != tmpBufNonReverbRight) {
produceLA32Output(nonReverbRight, len);
convertSamplesToOutput(nonReverbRight, len, false);
convertSamplesToOutput(nonReverbRight, len);
}
if (reverbDryLeft != tmpBufReverbDryLeft) convertSamplesToOutput(reverbDryLeft, len, false);
if (reverbDryRight != tmpBufReverbDryRight) convertSamplesToOutput(reverbDryRight, len, false);
if (reverbDryLeft != tmpBufReverbDryLeft) convertSamplesToOutput(reverbDryLeft, len);
if (reverbDryRight != tmpBufReverbDryRight) convertSamplesToOutput(reverbDryRight, len);
} else {
// Avoid muting buffers that wasn't requested
if (nonReverbLeft != tmpBufNonReverbLeft) muteSampleBuffer(nonReverbLeft, len);
if (nonReverbRight != tmpBufNonReverbRight) muteSampleBuffer(nonReverbRight, len);
if (reverbDryLeft != tmpBufReverbDryLeft) muteSampleBuffer(reverbDryLeft, len);
if (reverbDryRight != tmpBufReverbDryRight) muteSampleBuffer(reverbDryRight, len);
muteSampleBuffer(reverbWetLeft, len);
muteSampleBuffer(reverbWetRight, len);
}
@ -1651,14 +1687,48 @@ bool Synth::isActive() const {
return false;
}
const Partial *Synth::getPartial(unsigned int partialNum) const {
return partialManager->getPartial(partialNum);
}
unsigned int Synth::getPartialCount() const {
return partialCount;
}
void Synth::getPartStates(bool *partStates) const {
for (int partNumber = 0; partNumber < 9; partNumber++) {
const Part *part = parts[partNumber];
partStates[partNumber] = part->getActiveNonReleasingPartialCount() > 0;
}
}
void Synth::getPartialStates(PartialState *partialStates) const {
static const PartialState partialPhaseToState[8] = {
PartialState_ATTACK, PartialState_ATTACK, PartialState_ATTACK, PartialState_ATTACK,
PartialState_SUSTAIN, PartialState_SUSTAIN, PartialState_RELEASE, PartialState_INACTIVE
};
for (unsigned int partialNum = 0; partialNum < getPartialCount(); partialNum++) {
const Partial *partial = partialManager->getPartial(partialNum);
partialStates[partialNum] = partial->isActive() ? partialPhaseToState[partial->getTVA()->getPhase()] : PartialState_INACTIVE;
}
}
unsigned int Synth::getPlayingNotes(unsigned int partNumber, Bit8u *keys, Bit8u *velocities) const {
unsigned int playingNotes = 0;
if (isOpen && (partNumber < 9)) {
const Part *part = parts[partNumber];
const Poly *poly = part->getFirstActivePoly();
while (poly != NULL) {
keys[playingNotes] = (Bit8u)poly->getKey();
velocities[playingNotes] = (Bit8u)poly->getVelocity();
playingNotes++;
poly = poly->getNext();
}
}
return playingNotes;
}
const char *Synth::getPatchName(unsigned int partNumber) const {
return (!isOpen || partNumber > 8) ? NULL : parts[partNumber]->getCurrentInstr();
}
const Part *Synth::getPart(unsigned int partNum) const {
if (partNum > 8) {
return NULL;

View file

@ -19,15 +19,31 @@
#define MT32EMU_SYNTH_H
//#include <cstdarg>
//#include <cstring>
namespace MT32Emu {
class TableInitialiser;
class Analog;
class BReverbModel;
class MemoryRegion;
class MidiEventQueue;
class Part;
class Poly;
class Partial;
class PartialManager;
class Part;
class ROMImage;
class BReverbModel;
class PatchTempMemoryRegion;
class RhythmTempMemoryRegion;
class TimbreTempMemoryRegion;
class PatchesMemoryRegion;
class TimbresMemoryRegion;
class SystemMemoryRegion;
class DisplayMemoryRegion;
class ResetMemoryRegion;
struct ControlROMMap;
struct PCMWaveEntry;
struct MemParams;
/**
* Methods for emulating the connection between the LA32 and the DAC, which involves
@ -43,8 +59,7 @@ enum DACInputMode {
// Produces samples that exactly match the bits output from the emulated LA32.
// * Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range)
// * Much less likely to overdrive than any other mode.
// * Half the volume of any of the other modes, meaning its volume relative to the reverb
// output when mixed together directly will sound wrong.
// * Half the volume of any of the other modes.
// * Output gain is ignored for both LA32 and reverb output.
// * Perfect for developers while debugging :)
DACInputMode_PURE,
@ -60,6 +75,7 @@ enum DACInputMode {
DACInputMode_GENERATION2
};
// Methods for emulating the effective delay of incoming MIDI messages introduced by a MIDI interface.
enum MIDIDelayMode {
// Process incoming MIDI events immediately.
MIDIDelayMode_IMMEDIATE,
@ -72,6 +88,35 @@ enum MIDIDelayMode {
MIDIDelayMode_DELAY_ALL
};
// Methods for emulating the effects of analogue circuits of real hardware units on the output signal.
enum AnalogOutputMode {
// Only digital path is emulated. The output samples correspond to the digital signal at the DAC entrance.
AnalogOutputMode_DIGITAL_ONLY,
// Coarse emulation of LPF circuit. High frequencies are boosted, sample rate remains unchanged.
AnalogOutputMode_COARSE,
// Finer emulation of LPF circuit. Output signal is upsampled to 48 kHz to allow emulation of audible mirror spectra above 16 kHz,
// which is passed through the LPF circuit without significant attenuation.
AnalogOutputMode_ACCURATE,
// Same as AnalogOutputMode_ACCURATE mode but the output signal is 2x oversampled, i.e. the output sample rate is 96 kHz.
// This makes subsequent resampling easier. Besides, due to nonlinear passband of the LPF emulated, it takes fewer number of MACs
// compared to a regular LPF FIR implementations.
AnalogOutputMode_OVERSAMPLED
};
enum ReverbMode {
REVERB_MODE_ROOM,
REVERB_MODE_HALL,
REVERB_MODE_PLATE,
REVERB_MODE_TAP_DELAY
};
enum PartialState {
PartialState_INACTIVE,
PartialState_ATTACK,
PartialState_SUSTAIN,
PartialState_RELEASE
};
const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41;
const Bit8u SYSEX_MDL_MT32 = 0x16;
@ -87,148 +132,10 @@ 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 int MAX_SYSEX_SIZE = 512;
const int MAX_SYSEX_SIZE = 512; // FIXME: Does this correspond to a real MIDI buffer used in h/w devices?
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; // 4 * pcmCount bytes
Bit16u pcmCount;
Bit16u timbreAMap; // 128 bytes
Bit16u timbreAOffset;
bool timbreACompressed;
Bit16u timbreBMap; // 128 bytes
Bit16u timbreBOffset;
bool timbreBCompressed;
Bit16u timbreRMap; // 2 * timbreRCount bytes
Bit16u timbreRCount;
Bit16u rhythmSettings; // 4 * rhythmSettingsCount bytes
Bit16u rhythmSettingsCount;
Bit16u reserveSettings; // 9 bytes
Bit16u panSettings; // 8 bytes
Bit16u programSettings; // 8 bytes
Bit16u rhythmMaxTable; // 4 bytes
Bit16u patchMaxTable; // 16 bytes
Bit16u systemMaxTable; // 23 bytes
Bit16u timbreMaxTable; // 72 bytes
};
enum MemoryRegionType {
MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset
};
enum ReverbMode {
REVERB_MODE_ROOM,
REVERB_MODE_HALL,
REVERB_MODE_PLATE,
REVERB_MODE_TAP_DELAY
};
class MemoryRegion {
private:
Synth *synth;
Bit8u *realMemory;
Bit8u *maxTable;
public:
MemoryRegionType type;
Bit32u startAddr, entrySize, entries;
MemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable, MemoryRegionType useType, Bit32u useStartAddr, Bit32u useEntrySize, Bit32u useEntries) {
synth = useSynth;
realMemory = useRealMemory;
maxTable = useMaxTable;
type = useType;
startAddr = useStartAddr;
entrySize = useEntrySize;
entries = useEntries;
}
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;
}
Bit8u getMaxValue(int off) const {
if (maxTable == NULL)
return 0xFF;
return maxTable[off % entrySize];
}
Bit8u *getRealMemory() const {
return realMemory;
}
bool isReadable() const {
return getRealMemory() != NULL;
}
void read(unsigned int entry, unsigned int off, Bit8u *dst, unsigned int len) const;
void write(unsigned int entry, unsigned int off, const Bit8u *src, unsigned int len, bool init = false) const;
};
class PatchTempMemoryRegion : public MemoryRegion {
public:
PatchTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9) {}
};
class RhythmTempMemoryRegion : public MemoryRegion {
public:
RhythmTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85) {}
};
class TimbreTempMemoryRegion : public MemoryRegion {
public:
TimbreTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8) {}
};
class PatchesMemoryRegion : public MemoryRegion {
public:
PatchesMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128) {}
};
class TimbresMemoryRegion : public MemoryRegion {
public:
TimbresMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64) {}
};
class SystemMemoryRegion : public MemoryRegion {
public:
SystemMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::System), 1) {}
};
class DisplayMemoryRegion : public MemoryRegion {
public:
DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1) {}
};
class ResetMemoryRegion : public MemoryRegion {
public:
ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {}
};
class ReportHandler {
friend class Synth;
@ -254,47 +161,6 @@ protected:
virtual void onProgramChanged(int /* partNum */, int /* bankNum */, const char * /* patchName */) {}
};
/**
* Used to safely store timestamped MIDI events in a local queue.
*/
struct MidiEvent {
Bit32u shortMessageData;
const Bit8u *sysexData;
Bit32u sysexLength;
Bit32u timestamp;
~MidiEvent();
void setShortMessage(Bit32u shortMessageData, Bit32u timestamp);
void setSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp);
};
/**
* Simple queue implementation using a ring buffer to store incoming MIDI event before the synth actually processes it.
* It is intended to:
* - get rid of prerenderer while retaining graceful partial abortion
* - add fair emulation of the MIDI interface delays
* - extend the synth interface with the default implementation of a typical rendering loop.
* THREAD SAFETY:
* It is safe to use either in a single thread environment or when there are only two threads - one performs only reading
* and one performs only writing. More complicated usage requires external synchronisation.
*/
class MidiEventQueue {
private:
MidiEvent *ringBuffer;
Bit32u ringBufferSize;
volatile Bit32u startPosition;
volatile Bit32u endPosition;
public:
MidiEventQueue(Bit32u ringBufferSize = DEFAULT_MIDI_EVENT_QUEUE_SIZE);
~MidiEventQueue();
void reset();
bool pushShortMessage(Bit32u shortMessageData, Bit32u timestamp);
bool pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp);
const MidiEvent *peekMidiEvent();
void dropMidiEvent();
};
class Synth {
friend class Part;
friend class RhythmPart;
@ -335,7 +201,7 @@ private:
volatile Bit32u lastReceivedMIDIEventTimestamp;
volatile Bit32u renderedSampleCount;
MemParams mt32ram, mt32default;
MemParams &mt32ram, &mt32default;
BReverbModel *reverbModels[4];
BReverbModel *reverbModel;
@ -346,12 +212,6 @@ private:
float outputGain;
float reverbOutputGain;
#if MT32EMU_USE_FLOAT_SAMPLES
float effectiveReverbOutputGain;
#else
int effectiveOutputGain;
int effectiveReverbOutputGain;
#endif
bool reversedStereoEnabled;
@ -368,11 +228,12 @@ private:
// We emulate this by delaying new MIDI events processing until abortion finishes.
Poly *abortingPoly;
Bit32u getShortMessageLength(Bit32u msg);
Analog *analog;
Bit32u addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp);
void produceLA32Output(Sample *buffer, Bit32u len);
void convertSamplesToOutput(Sample *buffer, Bit32u len, bool reverb);
void convertSamplesToOutput(Sample *buffer, Bit32u len);
bool isAbortingPoly() const;
void doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len);
@ -404,13 +265,20 @@ private:
void newTimbreSet(int partNum, Bit8u timbreGroup, const char patchName[]);
void printDebug(const char *fmt, ...);
// partNum should be 0..7 for Part 1..8, or 8 for Rhythm
const Part *getPart(unsigned int partNum) const;
public:
static inline Bit16s clipBit16s(Bit32s sample) {
static inline Sample clipSampleEx(SampleEx sampleEx) {
#if MT32EMU_USE_FLOAT_SAMPLES
return sampleEx;
#else
// Clamp values above 32767 to 32767, and values below -32768 to -32768
// FIXME: Do we really need this stuff? I think these branches are very well predicted. Instead, this introduces a chain.
// The version below is actually a bit faster on my system...
//return ((sample + 0x8000) & ~0xFFFF) ? (sample >> 31) ^ 0x7FFF : (Bit16s)sample;
return ((-0x8000 <= sample) && (sample <= 0x7FFF)) ? (Bit16s)sample : (sample >> 31) ^ 0x7FFF;
//return ((sampleEx + 0x8000) & ~0xFFFF) ? (sampleEx >> 31) ^ 0x7FFF : (Sample)sampleEx;
return ((-0x8000 <= sampleEx) && (sampleEx <= 0x7FFF)) ? (Sample)sampleEx : (sampleEx >> 31) ^ 0x7FFF;
#endif
}
static inline void muteSampleBuffer(Sample *buffer, Bit32u len) {
@ -426,7 +294,8 @@ public:
#endif
}
static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum);
static Bit32u getShortMessageLength(Bit32u msg);
static Bit8u calcSysexChecksum(const Bit8u *data, const Bit32u len, const Bit8u initChecksum = 0);
// Optionally sets callbacks for reporting various errors, information and debug messages
Synth(ReportHandler *useReportHandler = NULL);
@ -435,8 +304,12 @@ public:
// Used to initialise the MT-32. Must be called before any other function.
// Returns true if initialization was sucessful, otherwise returns false.
// controlROMImage and pcmROMImage represent Control and PCM ROM images for use by synth.
// usePartialCount sets the maximum number of partials playing simultaneously for this session.
bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount = DEFAULT_MAX_PARTIALS);
// usePartialCount sets the maximum number of partials playing simultaneously for this session (optional).
// analogOutputMode sets the mode for emulation of analogue circuitry of the hardware units (optional).
bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount = DEFAULT_MAX_PARTIALS, AnalogOutputMode analogOutputMode = AnalogOutputMode_COARSE);
// Overloaded method which opens the synth with default partial count.
bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, AnalogOutputMode analogOutputMode);
// Closes the MT-32 and deallocates any memory used by the synthesizer
void close(bool forced = false);
@ -444,29 +317,34 @@ public:
// All the enqueued events are processed by the synth immediately.
void flushMIDIQueue();
// Sets size of the internal MIDI event queue.
// Sets size of the internal MIDI event queue. The queue size is set to the minimum power of 2 that is greater or equal to the size specified.
// The queue is flushed before reallocation.
void setMIDIEventQueueSize(Bit32u);
// Returns the actual queue size being used.
Bit32u setMIDIEventQueueSize(Bit32u);
// Enqueues a MIDI event for subsequent playback.
// The minimum delay involves the delay introduced while the event is transferred via MIDI interface
// and emulation of the MCU busy-loop while it frees partials for use by a new Poly.
// Calls from multiple threads must be synchronised, although,
// no synchronisation is required with the rendering thread.
// The MIDI event will be processed not before the specified timestamp.
// The timestamp is measured as the global rendered sample count since the synth was created.
// The timestamp is measured as the global rendered sample count since the synth was created (at the native sample rate 32000 Hz).
// The minimum delay involves emulation of the delay introduced while the event is transferred via MIDI interface
// and emulation of the MCU busy-loop while it frees partials for use by a new Poly.
// Calls from multiple threads must be synchronised, although, no synchronisation is required with the rendering thread.
// The methods return false if the MIDI event queue is full and the message cannot be enqueued.
// Enqueues a single short MIDI message. The message must contain a status byte.
bool playMsg(Bit32u msg, Bit32u timestamp);
// Enqueues a single well formed System Exclusive MIDI message.
bool playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp);
// The MIDI event will be processed ASAP.
// Overloaded methods for the MIDI events to be processed ASAP.
bool playMsg(Bit32u msg);
bool playSysex(const Bit8u *sysex, Bit32u len);
// WARNING:
// The methods below don't ensure minimum 1-sample delay between sequential MIDI events,
// and a sequence of NoteOn and immediately succeeding NoteOff messages is always silent.
// A thread that invokes these methods must be explicitly synchronised with the thread performing sample rendering.
// Sends a 4-byte MIDI message to the MT-32 for immediate playback.
// Sends a short MIDI message to the synth for immediate playback. The message must contain a status byte.
void playMsgNow(Bit32u msg);
void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity);
@ -495,12 +373,17 @@ public:
void setMIDIDelayMode(MIDIDelayMode mode);
MIDIDelayMode getMIDIDelayMode() const;
// Sets output gain factor. Applied to all output samples and unrelated with the synth's Master volume.
// Sets output gain factor for synth output channels. Applied to all output samples and unrelated with the synth's Master volume,
// it rather corresponds to the gain of the output analog circuitry of the hardware units. However, together with setReverbOutputGain()
// it offers to the user a capability to control the gain of reverb and non-reverb output channels independently.
// Ignored in DACInputMode_PURE
void setOutputGain(float);
float getOutputGain() const;
// Sets output gain factor for the reverb wet output. setOutputGain() doesn't change reverb output gain.
// Sets output gain factor for the reverb wet output channels. It rather corresponds to the gain of the output
// analog circuitry of the hardware units. However, together with setOutputGain() it offers to the user a capability
// to control the gain of reverb and non-reverb output channels independently.
//
// Note: We're currently emulate CM-32L/CM-64 reverb quite accurately and the reverb output level closely
// corresponds to the level of digital capture. Although, according to the CM-64 PCB schematic,
// there is a difference in the reverb analogue circuit, and the resulting output gain is 0.68
@ -512,12 +395,21 @@ public:
void setReversedStereoEnabled(bool enabled);
bool isReversedStereoEnabled();
// Renders samples to the specified output stream.
// The length is in frames, not bytes (in 16-bit stereo,
// one frame is 4 bytes).
// Returns actual sample rate used in emulation of stereo analog circuitry of hardware units.
// See comment for render() below.
unsigned int getStereoOutputSampleRate() const;
// Renders samples to the specified output stream as if they were sampled at the analog stereo output.
// When AnalogOutputMode is set to ACCURATE, the output signal is upsampled to 48 kHz in order
// to retain emulation accuracy in whole audible frequency spectra. Otherwise, native digital signal sample rate is retained.
// getStereoOutputSampleRate() can be used to query actual sample rate of the output signal.
// The length is in frames, not bytes (in 16-bit stereo, one frame is 4 bytes).
void render(Sample *stream, Bit32u len);
// Renders samples to the specified output streams (any or all of which may be NULL).
// Renders samples to the specified output streams as if they appeared at the DAC entrance.
// No further processing performed in analog circuitry emulation is applied to the signal.
// NULL may be specified in place of any or all of the stream buffers.
// The length is in samples, not bytes.
void renderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len);
// Returns true when there is at least one active partial, otherwise false.
@ -526,15 +418,28 @@ public:
// Returns true if hasActivePartials() returns true, or reverb is (somewhat unreliably) detected as being active.
bool isActive() const;
const Partial *getPartial(unsigned int partialNum) const;
// Returns the maximum number of partials playing simultaneously.
unsigned int getPartialCount() const;
void readMemory(Bit32u addr, Bit32u len, Bit8u *data);
// Fills in current states of all the parts into the array provided. The array must have at least 9 entries to fit values for all the parts.
// If the value returned for a part is true, there is at least one active non-releasing partial playing on this part.
// This info is useful in emulating behaviour of LCD display of the hardware units.
void getPartStates(bool *partStates) const;
// partNum should be 0..7 for Part 1..8, or 8 for Rhythm
const Part *getPart(unsigned int partNum) const;
// Fills in current states of all the partials into the array provided. The array must be large enough to accommodate states of all the partials.
void getPartialStates(PartialState *partialStates) const;
// Fills in information about currently playing notes on the specified part into the arrays provided. The arrays must be large enough
// to accommodate data for all the playing notes. The maximum number of simultaneously playing notes cannot exceed the number of partials.
// Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
// Returns the number of currently playing notes on the specified part.
unsigned int getPlayingNotes(unsigned int partNumber, Bit8u *keys, Bit8u *velocities) const;
// Returns name of the patch set on the specified part.
// Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
const char *getPatchName(unsigned int partNumber) const;
void readMemory(Bit32u addr, Bit32u len, Bit8u *data);
};
}

View file

@ -23,6 +23,7 @@
#include "mt32emu.h"
#include "mmath.h"
#include "internals.h"
namespace MT32Emu {

View file

@ -19,6 +19,7 @@
#include "mt32emu.h"
#include "mmath.h"
#include "internals.h"
namespace MT32Emu {

View file

@ -19,6 +19,7 @@
//#include <cstdlib>
#include "mt32emu.h"
#include "internals.h"
namespace MT32Emu {

View file

@ -16,14 +16,15 @@
*/
//#include <cmath>
//#include <cstdlib>
//#include <cstring>
#include "mt32emu.h"
#include "mmath.h"
#include "Tables.h"
namespace MT32Emu {
// UNUSED: const int MIDDLEC = 60;
const Tables &Tables::getInstance() {
static const Tables instance;
return instance;

View file

@ -20,24 +20,11 @@
namespace MT32Emu {
// Sample rate to use in mixing. With the progress of development, we've found way too many thing dependent.
// In order to achieve further advance in emulation accuracy, sample rate made fixed throughout the emulator.
// The output from the synth is supposed to be resampled to convert the sample rate.
const unsigned int SAMPLE_RATE = 32000;
// MIDI interface data transfer rate in samples. Used to simulate the transfer delay.
const double MIDI_DATA_TRANSFER_RATE = (double)SAMPLE_RATE / 31250.0 * 8.0;
const float CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR = 0.68f;
const int MIDDLEC = 60;
class Synth;
class Tables {
private:
Tables();
Tables(Tables &);
~Tables() {}
public:
static const Tables &getInstance();

View file

@ -0,0 +1,40 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_TYPES_H
#define MT32EMU_TYPES_H
namespace MT32Emu {
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;
#if MT32EMU_USE_FLOAT_SAMPLES
typedef float Sample;
typedef float SampleEx;
#else
typedef Bit16s Sample;
typedef Bit32s SampleEx;
#endif
}
#endif

View file

@ -0,0 +1,83 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_INTERNALS_H
#define MT32EMU_INTERNALS_H
// Debugging
// 0: Standard debug output is not stamped with the rendered sample count
// 1: Standard debug output is stamped with the rendered sample count
// NOTE: The "samplestamp" corresponds to the end of the last completed rendering run.
// This is important to bear in mind for debug output that occurs during a run.
#define MT32EMU_DEBUG_SAMPLESTAMPS 0
// 0: No debug output for initialisation progress
// 1: Debug output for initialisation progress
#define MT32EMU_MONITOR_INIT 0
// 0: No debug output for MIDI events
// 1: Debug output for weird MIDI events
#define MT32EMU_MONITOR_MIDI 0
// 0: No debug output for note on/off
// 1: Basic debug output for note on/off
// 2: Comprehensive debug output for note on/off
#define MT32EMU_MONITOR_INSTRUMENTS 0
// 0: No debug output for partial allocations
// 1: Show partial stats when an allocation fails
// 2: Show partial stats with every new poly
// 3: Show individual partial allocations/deactivations
#define MT32EMU_MONITOR_PARTIALS 0
// 0: No debug output for sysex
// 1: Basic debug output for sysex
#define MT32EMU_MONITOR_SYSEX 0
// 0: No debug output for sysex writes to the timbre areas
// 1: Debug output with the name and location of newly-written timbres
// 2: Complete dump of timbre parameters for newly-written timbres
#define MT32EMU_MONITOR_TIMBRES 0
// 0: No TVA/TVF-related debug output.
// 1: Shows changes to TVA/TVF target, increment and phase.
#define MT32EMU_MONITOR_TVA 0
#define MT32EMU_MONITOR_TVF 0
// Configuration
// If non-zero, deletes reverb buffers that are not in use to save memory.
// If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path.
#define MT32EMU_REDUCE_REVERB_MEMORY 1
// 0: Maximum speed at the cost of a bit lower emulation accuracy.
// 1: Maximum achievable emulation accuracy.
#define MT32EMU_BOSS_REVERB_PRECISE_MODE 0
#include "Structures.h"
#include "Tables.h"
#include "Poly.h"
#include "LA32Ramp.h"
#include "LA32WaveGenerator.h"
#include "TVA.h"
#include "TVP.h"
#include "TVF.h"
#include "Partial.h"
#include "Part.h"
#endif

View file

@ -1,6 +1,7 @@
MODULE := audio/softsynth/mt32
MODULE_OBJS := \
Analog.o \
BReverbModel.o \
LA32Ramp.o \
LA32WaveGenerator.o \

View file

@ -18,63 +18,20 @@
#ifndef MT32EMU_MT32EMU_H
#define MT32EMU_MT32EMU_H
// Debugging
// 0: Standard debug output is not stamped with the rendered sample count
// 1: Standard debug output is stamped with the rendered sample count
// NOTE: The "samplestamp" corresponds to the end of the last completed rendering run.
// This is important to bear in mind for debug output that occurs during a run.
#define MT32EMU_DEBUG_SAMPLESTAMPS 0
// 0: No debug output for initialisation progress
// 1: Debug output for initialisation progress
#define MT32EMU_MONITOR_INIT 0
// 0: No debug output for MIDI events
// 1: Debug output for weird MIDI events
#define MT32EMU_MONITOR_MIDI 0
// 0: No debug output for note on/off
// 1: Basic debug output for note on/off
// 2: Comprehensive debug output for note on/off
#define MT32EMU_MONITOR_INSTRUMENTS 0
// 0: No debug output for partial allocations
// 1: Show partial stats when an allocation fails
// 2: Show partial stats with every new poly
// 3: Show individual partial allocations/deactivations
#define MT32EMU_MONITOR_PARTIALS 0
// 0: No debug output for sysex
// 1: Basic debug output for sysex
#define MT32EMU_MONITOR_SYSEX 0
// 0: No debug output for sysex writes to the timbre areas
// 1: Debug output with the name and location of newly-written timbres
// 2: Complete dump of timbre parameters for newly-written timbres
#define MT32EMU_MONITOR_TIMBRES 0
// 0: No TVA/TVF-related debug output.
// 1: Shows changes to TVA/TVF target, increment and phase.
#define MT32EMU_MONITOR_TVA 0
#define MT32EMU_MONITOR_TVF 0
// Configuration
// If non-zero, deletes reverb buffers that are not in use to save memory.
// If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path.
#define MT32EMU_REDUCE_REVERB_MEMORY 1
// 0: Maximum speed at the cost of a bit lower emulation accuracy.
// 1: Maximum achievable emulation accuracy.
#define MT32EMU_BOSS_REVERB_PRECISE_MODE 0
// 0: Use 16-bit signed samples and refined wave generator based on logarithmic fixed-point computations and LUTs. Maximum emulation accuracy and speed.
// 1: Use float samples in the wave generator and renderer. Maximum output quality and minimum noise.
#define MT32EMU_USE_FLOAT_SAMPLES 0
namespace MT32Emu
{
// Sample rate to use in mixing. With the progress of development, we've found way too many thing dependent.
// In order to achieve further advance in emulation accuracy, sample rate made fixed throughout the emulator,
// except the emulation of analogue path.
// The output from the synth is supposed to be resampled externally in order to convert to the desired sample rate.
const unsigned int SAMPLE_RATE = 32000;
// The default value for the maximum number of partials playing simultaneously.
const unsigned int DEFAULT_MAX_PARTIALS = 32;
@ -97,17 +54,7 @@ const unsigned int MAX_SAMPLES_PER_RUN = 4096;
const unsigned int DEFAULT_MIDI_EVENT_QUEUE_SIZE = 1024;
}
#include "Structures.h"
#include "common/file.h"
#include "Tables.h"
#include "Poly.h"
#include "LA32Ramp.h"
#include "LA32WaveGenerator.h"
#include "TVA.h"
#include "TVP.h"
#include "TVF.h"
#include "Partial.h"
#include "Part.h"
#include "Types.h"
#include "ROMInfo.h"
#include "Synth.h"

View file

@ -32,6 +32,7 @@
#include "dosbox.h"
#include "dbopl.h"
#include "audio/mixer.h"
#include "common/system.h"
#include "common/scummsys.h"
#include "common/util.h"
@ -148,6 +149,7 @@ OPL::OPL(Config::OplType type) : _type(type), _rate(0), _emulator(0) {
}
OPL::~OPL() {
stop();
free();
}
@ -156,7 +158,7 @@ void OPL::free() {
_emulator = 0;
}
bool OPL::init(int rate) {
bool OPL::init() {
free();
memset(&_reg, 0, sizeof(_reg));
@ -167,19 +169,19 @@ bool OPL::init(int rate) {
return false;
DBOPL::InitTables();
_emulator->Setup(rate);
_rate = g_system->getMixer()->getOutputRate();
_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);
init();
}
void OPL::write(int port, int val) {
@ -307,7 +309,7 @@ void OPL::dualWrite(uint8 index, uint8 reg, uint8 val) {
_emulator->WriteReg(fullReg, val);
}
void OPL::readBuffer(int16 *buffer, int length) {
void OPL::generateSamples(int16 *buffer, int length) {
// For stereo OPL cards, we divide the sample count by 2,
// to match stereo AudioStream behavior.
if (_type != Config::kOpl2)

View file

@ -69,7 +69,7 @@ namespace DBOPL {
struct Chip;
} // end of namespace DBOPL
class OPL : public ::OPL::OPL {
class OPL : public ::OPL::EmulatedOPL {
private:
Config::OplType _type;
uint _rate;
@ -87,7 +87,7 @@ public:
OPL(Config::OplType type);
~OPL();
bool init(int rate);
bool init();
void reset();
void write(int a, int v);
@ -95,8 +95,10 @@ public:
void writeReg(int r, int v);
void readBuffer(int16 *buffer, int length);
bool isStereo() const { return _type != Config::kOpl2; }
protected:
void generateSamples(int16 *buffer, int length);
};
} // End of namespace DOSBox

View file

@ -31,6 +31,8 @@
#include "mame.h"
#include "audio/mixer.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
@ -46,15 +48,19 @@ namespace OPL {
namespace MAME {
OPL::~OPL() {
stop();
MAME::OPLDestroy(_opl);
_opl = 0;
}
bool OPL::init(int rate) {
if (_opl)
bool OPL::init() {
if (_opl) {
stopCallbacks();
MAME::OPLDestroy(_opl);
}
_opl = MAME::makeAdLibOPL(g_system->getMixer()->getOutputRate());
_opl = MAME::makeAdLibOPL(rate);
return (_opl != 0);
}
@ -74,7 +80,7 @@ void OPL::writeReg(int r, int v) {
MAME::OPLWriteReg(_opl, r, v);
}
void OPL::readBuffer(int16 *buffer, int length) {
void OPL::generateSamples(int16 *buffer, int length) {
MAME::YM3812UpdateOne(_opl, buffer, length);
}

View file

@ -174,14 +174,14 @@ void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length);
FM_OPL *makeAdLibOPL(int rate);
// OPL API implementation
class OPL : public ::OPL::OPL {
class OPL : public ::OPL::EmulatedOPL {
private:
FM_OPL *_opl;
public:
OPL() : _opl(0) {}
~OPL();
bool init(int rate);
bool init();
void reset();
void write(int a, int v);
@ -189,8 +189,10 @@ public:
void writeReg(int r, int v);
void readBuffer(int16 *buffer, int length);
bool isStereo() const { return false; }
protected:
void generateSamples(int16 *buffer, int length);
};
} // End of namespace MAME

View file

@ -39,12 +39,10 @@ Timestamp::Timestamp(uint ms, uint fr) {
Timestamp::Timestamp(uint s, uint frames, uint fr) {
assert(fr > 0);
_secs = s;
_secs = s + (frames / fr);
_framerateFactor = 1000 / Common::gcd<uint>(1000, fr);
_framerate = fr * _framerateFactor;
_numFrames = frames * _framerateFactor;
normalize();
_numFrames = (frames % fr) * _framerateFactor;
}
Timestamp Timestamp::convertToFramerate(uint newFramerate) const {

View file

@ -24,9 +24,12 @@
#if defined(SDL_BACKEND)
#include "common/textconsole.h"
#include "backends/audiocd/sdl/sdl-audiocd.h"
#if !SDL_VERSION_ATLEAST(1, 3, 0)
#include "common/textconsole.h"
SdlAudioCDManager::SdlAudioCDManager()
:
_cdrom(0),
@ -133,4 +136,6 @@ void SdlAudioCDManager::updateCD() {
}
}
#endif // !SDL_VERSION_ATLEAST(1, 3, 0)
#endif

View file

@ -27,6 +27,8 @@
#include "backends/platform/sdl/sdl-sys.h"
#if !SDL_VERSION_ATLEAST(1, 3, 0)
/**
* The SDL audio cd manager. Implements real audio cd playback.
*/
@ -47,4 +49,6 @@ protected:
uint32 _cdEndTime, _cdStopTime;
};
#endif // !SDL_VERSION_ATLEAST(1, 3, 0)
#endif

View file

@ -49,22 +49,55 @@
#define JOY_BUT_SPACE 4
#define JOY_BUT_F5 5
#if SDL_VERSION_ATLEAST(2, 0, 0)
static uint32 convUTF8ToUTF32(const char *src) {
uint32 utf32 = 0;
char *dst = SDL_iconv_string(
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
"UTF-32BE",
#else
"UTF-32LE",
#endif
"UTF-8", src, SDL_strlen(src) + 1);
if (dst) {
utf32 = *((uint32 *)dst);
SDL_free(dst);
}
return utf32;
}
#endif
SdlEventSource::SdlEventSource()
: EventSource(), _scrollLock(false), _joystick(0), _lastScreenID(0), _graphicsManager(0) {
: EventSource(), _scrollLock(false), _joystick(0), _lastScreenID(0), _graphicsManager(0)
#if SDL_VERSION_ATLEAST(2, 0, 0)
, _queuedFakeKeyUp(false), _fakeKeyUp()
#endif
{
// Reset mouse state
memset(&_km, 0, sizeof(_km));
int joystick_num = ConfMan.getInt("joystick_num");
if (joystick_num > -1) {
if (joystick_num >= 0) {
// Initialize SDL joystick subsystem
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) == -1) {
error("Could not initialize SDL: %s", SDL_GetError());
}
// Enable joystick
if (SDL_NumJoysticks() > 0) {
debug("Using joystick: %s", SDL_JoystickName(0));
if (SDL_NumJoysticks() > joystick_num) {
_joystick = SDL_JoystickOpen(joystick_num);
debug("Using joystick: %s",
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_JoystickName(_joystick)
#else
SDL_JoystickName(joystick_num)
#endif
);
} else {
warning("Invalid joystick: %d", joystick_num);
}
}
}
@ -74,21 +107,24 @@ SdlEventSource::~SdlEventSource() {
SDL_JoystickClose(_joystick);
}
int SdlEventSource::mapKey(SDLKey key, SDLMod mod, Uint16 unicode) {
if (key >= SDLK_F1 && key <= SDLK_F9) {
int SdlEventSource::mapKey(SDLKey sdlKey, SDLMod mod, Uint16 unicode) {
Common::KeyCode key = SDLToOSystemKeycode(sdlKey);
if (key >= Common::KEYCODE_F1 && key <= Common::KEYCODE_F9) {
return key - SDLK_F1 + Common::ASCII_F1;
} else if (key >= SDLK_KP0 && key <= SDLK_KP9) {
return key - SDLK_KP0 + '0';
} else if (key >= SDLK_UP && key <= SDLK_PAGEDOWN) {
} else if (key >= Common::KEYCODE_KP0 && key <= Common::KEYCODE_KP9) {
return key - Common::KEYCODE_KP0 + '0';
} else if (key >= Common::KEYCODE_UP && key <= Common::KEYCODE_PAGEDOWN) {
return key;
} else if (unicode) {
return unicode;
} else if (key >= 'a' && key <= 'z' && (mod & KMOD_SHIFT)) {
return key & ~0x20;
} else if (key >= SDLK_NUMLOCK && key <= SDLK_EURO) {
} else if (key >= Common::KEYCODE_NUMLOCK && key <= Common::KEYCODE_EURO) {
return 0;
} else {
return key;
}
return key;
}
void SdlEventSource::processMouseEvent(Common::Event &event, int x, int y) {
@ -171,7 +207,9 @@ void SdlEventSource::handleKbdMouse() {
_km.y_down_count = 1;
}
SDL_WarpMouse((Uint16)_km.x, (Uint16)_km.y);
if (_graphicsManager) {
_graphicsManager->getWindow()->warpMouseInWindow((Uint16)_km.x, (Uint16)_km.y);
}
}
}
}
@ -338,7 +376,9 @@ Common::KeyCode SdlEventSource::SDLToOSystemKeycode(const SDLKey key) {
case SDLK_HELP: return Common::KEYCODE_HELP;
case SDLK_PRINT: return Common::KEYCODE_PRINT;
case SDLK_SYSREQ: return Common::KEYCODE_SYSREQ;
#if !SDL_VERSION_ATLEAST(2, 0, 0)
case SDLK_BREAK: return Common::KEYCODE_BREAK;
#endif
case SDLK_MENU: return Common::KEYCODE_MENU;
case SDLK_POWER: return Common::KEYCODE_POWER;
case SDLK_UNDO: return Common::KEYCODE_UNDO;
@ -349,6 +389,16 @@ Common::KeyCode SdlEventSource::SDLToOSystemKeycode(const SDLKey key) {
bool SdlEventSource::pollEvent(Common::Event &event) {
handleKbdMouse();
#if SDL_VERSION_ATLEAST(2, 0, 0)
// In case we still need to send a key up event for a key down from a
// TEXTINPUT event we do this immediately.
if (_queuedFakeKeyUp) {
event = _fakeKeyUp;
_queuedFakeKeyUp = false;
return true;
}
#endif
// If the screen changed, send an Common::EVENT_SCREEN_CHANGED
int screenID = ((OSystem_SDL *)g_system)->getGraphicsManager()->getScreenChangeID();
if (screenID != _lastScreenID) {
@ -385,24 +435,73 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
case SDL_JOYAXISMOTION:
return handleJoyAxisMotion(ev, event);
#if SDL_VERSION_ATLEAST(2, 0, 0)
case SDL_MOUSEWHEEL: {
Sint32 yDir = ev.wheel.y;
#if SDL_VERSION_ATLEAST(2, 0, 4)
if (ev.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) {
yDir *= -1;
}
#endif
// HACK: It seems we want the mouse coordinates supplied
// with a mouse wheel event. However, SDL2 does not supply
// these, thus we use whatever we got last time. It seems
// these are always stored in _km.x, _km.y.
processMouseEvent(event, _km.x, _km.y);
if (yDir < 0) {
event.type = Common::EVENT_WHEELDOWN;
return true;
} else if (yDir > 0) {
event.type = Common::EVENT_WHEELUP;
return true;
} else {
return false;
}
}
case SDL_TEXTINPUT: {
// When we get a TEXTINPUT event it means we got some user input for
// which no KEYDOWN exists. SDL 1.2 introduces a "fake" key down+up
// in such cases. We will do the same to mimic it's behavior.
event.type = Common::EVENT_KEYDOWN;
event.kbd = Common::KeyState(Common::KEYCODE_INVALID, convUTF8ToUTF32(ev.text.text), 0);
SDLModToOSystemKeyFlags(SDL_GetModState(), event);
// Set the scroll lock sticky flag
if (_scrollLock)
event.kbd.flags |= Common::KBD_SCRL;
// Fake a key up when we have a proper ascii value.
_queuedFakeKeyUp = (event.kbd.ascii != 0);
_fakeKeyUp = event;
_fakeKeyUp.type = Common::EVENT_KEYUP;
return _queuedFakeKeyUp;
}
case SDL_WINDOWEVENT:
switch (ev.window.event) {
case SDL_WINDOWEVENT_EXPOSED:
if (_graphicsManager)
_graphicsManager->notifyVideoExpose();
return false;
case SDL_WINDOWEVENT_RESIZED:
return handleResizeEvent(event, ev.window.data1, ev.window.data2);
default:
return false;
}
#else
case SDL_VIDEOEXPOSE:
if (_graphicsManager)
_graphicsManager->notifyVideoExpose();
return false;
case SDL_VIDEORESIZE:
if (_graphicsManager) {
_graphicsManager->notifyResize(ev.resize.w, ev.resize.h);
// If the screen changed, send an Common::EVENT_SCREEN_CHANGED
int screenID = ((OSystem_SDL *)g_system)->getGraphicsManager()->getScreenChangeID();
if (screenID != _lastScreenID) {
_lastScreenID = screenID;
event.type = Common::EVENT_SCREEN_CHANGED;
return true;
}
}
return false;
return handleResizeEvent(event, ev.resize.w, ev.resize.h);
#endif
case SDL_QUIT:
event.type = Common::EVENT_QUIT;
@ -427,7 +526,9 @@ bool SdlEventSource::handleKeyDown(SDL_Event &ev, Common::Event &event) {
// Ctrl-m toggles mouse capture
if (event.kbd.hasFlags(Common::KBD_CTRL) && ev.key.keysym.sym == 'm') {
toggleMouseGrab();
if (_graphicsManager) {
_graphicsManager->getWindow()->toggleMouseGrab();
}
return false;
}
@ -470,7 +571,7 @@ bool SdlEventSource::handleKeyDown(SDL_Event &ev, Common::Event &event) {
event.type = Common::EVENT_KEYDOWN;
event.kbd.keycode = SDLToOSystemKeycode(ev.key.keysym.sym);
event.kbd.ascii = mapKey(ev.key.keysym.sym, (SDLMod)ev.key.keysym.mod, (Uint16)ev.key.keysym.unicode);
event.kbd.ascii = mapKey(ev.key.keysym.sym, (SDLMod)ev.key.keysym.mod, obtainUnicode(ev.key.keysym));
return true;
}
@ -514,7 +615,7 @@ bool SdlEventSource::handleKeyUp(SDL_Event &ev, Common::Event &event) {
event.type = Common::EVENT_KEYUP;
event.kbd.keycode = SDLToOSystemKeycode(ev.key.keysym.sym);
event.kbd.ascii = mapKey(ev.key.keysym.sym, (SDLMod)ev.key.keysym.mod, (Uint16)ev.key.keysym.unicode);
event.kbd.ascii = mapKey(ev.key.keysym.sym, (SDLMod)ev.key.keysym.mod, 0);
// Ctrl-Alt-<key> will change the GFX mode
SDLModToOSystemKeyFlags(mod, event);
@ -750,13 +851,6 @@ bool SdlEventSource::remapKey(SDL_Event &ev, Common::Event &event) {
return false;
}
void SdlEventSource::toggleMouseGrab() {
if (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_OFF)
SDL_WM_GrabInput(SDL_GRAB_ON);
else
SDL_WM_GrabInput(SDL_GRAB_OFF);
}
void SdlEventSource::resetKeyboadEmulation(int16 x_max, int16 y_max) {
_km.x_max = x_max;
_km.y_max = y_max;
@ -764,4 +858,52 @@ void SdlEventSource::resetKeyboadEmulation(int16 x_max, int16 y_max) {
_km.last_time = 0;
}
bool SdlEventSource::handleResizeEvent(Common::Event &event, int w, int h) {
if (_graphicsManager) {
_graphicsManager->notifyResize(w, h);
// If the screen changed, send an Common::EVENT_SCREEN_CHANGED
int screenID = ((OSystem_SDL *)g_system)->getGraphicsManager()->getScreenChangeID();
if (screenID != _lastScreenID) {
_lastScreenID = screenID;
event.type = Common::EVENT_SCREEN_CHANGED;
return true;
}
}
return false;
}
uint32 SdlEventSource::obtainUnicode(const SDL_keysym keySym) {
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_Event events[2];
// In SDL2, the unicode field has been removed from the keysym struct.
// Instead a SDL_TEXTINPUT event is generated on key combinations that
// generates unicode.
// Here we peek into the event queue for the event to see if it exists.
int n = SDL_PeepEvents(events, 2, SDL_PEEKEVENT, SDL_KEYDOWN, SDL_TEXTINPUT);
// Make sure that the TEXTINPUT event belongs to this KEYDOWN
// event and not another pending one.
if ((n > 0 && events[0].type == SDL_TEXTINPUT)
|| (n > 1 && events[0].type != SDL_KEYDOWN && events[1].type == SDL_TEXTINPUT)) {
// Remove the text input event we associate with the key press. This
// makes sure we never get any SDL_TEXTINPUT events which do "belong"
// to SDL_KEYDOWN events.
n = SDL_PeepEvents(events, 1, SDL_GETEVENT, SDL_TEXTINPUT, SDL_TEXTINPUT);
// This is basically a paranoia safety check because we know there
// must be a text input event in the queue.
if (n > 0) {
return convUTF8ToUTF32(events[0].text.text);
} else {
return 0;
}
} else {
return 0;
}
#else
return keySym.unicode;
#endif
}
#endif

View file

@ -49,11 +49,6 @@ public:
*/
virtual void resetKeyboadEmulation(int16 x_max, int16 y_max);
/**
* Toggles mouse input grab
*/
virtual void toggleMouseGrab();
protected:
/** @name Keyboard mouse emulation
* Disabled by fingolfin 2004-12-18.
@ -130,7 +125,7 @@ protected:
/**
* Maps the ASCII value of key
*/
virtual int mapKey(SDLKey key, SDLMod mod, Uint16 unicode);
int mapKey(SDLKey key, SDLMod mod, Uint16 unicode);
/**
* Configures the key modifiers flags status
@ -141,6 +136,30 @@ protected:
* Translates SDL key codes to OSystem key codes
*/
Common::KeyCode SDLToOSystemKeycode(const SDLKey key);
/**
* Notify graphics manager of a resize request.
*/
bool handleResizeEvent(Common::Event &event, int w, int h);
/**
* Extracts unicode information for the specific key sym.
* May only be used for key down events.
*/
uint32 obtainUnicode(const SDL_keysym keySym);
#if SDL_VERSION_ATLEAST(2, 0, 0)
/**
* Whether _fakeKeyUp contains an event we need to send.
*/
bool _queuedFakeKeyUp;
/**
* A fake key up event when we receive a TEXTINPUT without any previous
* KEYDOWN event.
*/
Common::Event _fakeKeyUp;
#endif
};
#endif

View file

@ -133,7 +133,9 @@ bool SymbianSdlEventSource::remapKey(SDL_Event &ev, Common::Event &event) {
_currentZone = 0;
event.type = Common::EVENT_MOUSEMOVE;
processMouseEvent(event, _mouseXZone[_currentZone], _mouseYZone[_currentZone]);
SDL_WarpMouse(event.mouse.x, event.mouse.y);
if (_graphicsManager) {
_graphicsManager->getWindow()->warpMouseInWindow(event.mouse.x, event.mouse.y);
}
}
return true;

View file

@ -381,15 +381,17 @@ AbstractFSList AmigaOSFilesystemNode::listVolumes() const {
dosList = IDOS->NextDosEntry(dosList, LDF_VOLUMES);
while (dosList) {
if (dosList->dol_Type == DLT_VOLUME &&
dosList->dol_Name) {
dosList->dol_Name &&
dosList->dol_Port) {
// The original line was
//if (dosList->dol_Type == DLT_VOLUME &&
//dosList->dol_Name &&
//dosList->dol_Task) {
// which errored using SDK 53.24 with a 'struct dosList' has no member called 'dol_Task'
// I removed dol_Task because it's not used anywhere else
// and it neither brought up further errors nor crashes or regressions
// The reason for that was that
// 1) dol_Task wasn't a task pointer, it is a message port instead
// 2) It was redefined to be dol_Port in dos/obsolete.h in afore mentioned SDK
// Copy name to buffer
IDOS->CopyStringBSTRToC(dosList->dol_Name, buffer, MAXPATHLEN);

View file

@ -35,8 +35,8 @@ static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
{0, 0, 0}
};
DINGUXSdlGraphicsManager::DINGUXSdlGraphicsManager(SdlEventSource *boss)
: SurfaceSdlGraphicsManager(boss) {
DINGUXSdlGraphicsManager::DINGUXSdlGraphicsManager(SdlEventSource *boss, SdlWindow *window)
: SurfaceSdlGraphicsManager(boss, window) {
}
const OSystem::GraphicsMode *DINGUXSdlGraphicsManager::getSupportedGraphicsModes() const {
@ -122,7 +122,7 @@ void DINGUXSdlGraphicsManager::initSize(uint w, uint h) {
if (w > 320 || h > 240) {
setGraphicsMode(GFX_HALF);
setGraphicsModeIntern();
_eventSource->toggleMouseGrab();
_window->toggleMouseGrab();
}
_transactionDetails.sizeChanged = true;

View file

@ -34,7 +34,7 @@ enum {
class DINGUXSdlGraphicsManager : public SurfaceSdlGraphicsManager {
public:
DINGUXSdlGraphicsManager(SdlEventSource *boss);
DINGUXSdlGraphicsManager(SdlEventSource *boss, SdlWindow *window);
bool hasFeature(OSystem::Feature f);
void setFeatureState(OSystem::Feature f, bool enable);

View file

@ -35,8 +35,8 @@ static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
{0, 0, 0}
};
GPHGraphicsManager::GPHGraphicsManager(SdlEventSource *sdlEventSource)
: SurfaceSdlGraphicsManager(sdlEventSource) {
GPHGraphicsManager::GPHGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window)
: SurfaceSdlGraphicsManager(sdlEventSource, window) {
}
const OSystem::GraphicsMode *GPHGraphicsManager::getSupportedGraphicsModes() const {
@ -141,7 +141,7 @@ void GPHGraphicsManager::initSize(uint w, uint h, const Graphics::PixelFormat *f
if (w > 320 || h > 240) {
setGraphicsMode(GFX_HALF);
setGraphicsModeIntern();
_eventSource->toggleMouseGrab();
_window->toggleMouseGrab();
}
_videoMode.overlayWidth = 320;

View file

@ -33,7 +33,7 @@ enum {
class GPHGraphicsManager : public SurfaceSdlGraphicsManager {
public:
GPHGraphicsManager(SdlEventSource *boss);
GPHGraphicsManager(SdlEventSource *boss, SdlWindow *window);
bool hasFeature(OSystem::Feature f);
void setFeatureState(OSystem::Feature f, bool enable);

View file

@ -45,8 +45,8 @@ static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
{0, 0, 0}
};
LinuxmotoSdlGraphicsManager::LinuxmotoSdlGraphicsManager(SdlEventSource *sdlEventSource)
: SurfaceSdlGraphicsManager(sdlEventSource) {
LinuxmotoSdlGraphicsManager::LinuxmotoSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window)
: SurfaceSdlGraphicsManager(sdlEventSource, window) {
}
const OSystem::GraphicsMode *LinuxmotoSdlGraphicsManager::getSupportedGraphicsModes() const {
@ -134,7 +134,7 @@ void LinuxmotoSdlGraphicsManager::initSize(uint w, uint h) {
if (w > 320 || h > 240) {
setGraphicsMode(GFX_HALF);
setGraphicsModeIntern();
_eventSource->toggleMouseGrab();
_window->toggleMouseGrab();
}
_transactionDetails.sizeChanged = true;

View file

@ -27,7 +27,7 @@
class LinuxmotoSdlGraphicsManager : public SurfaceSdlGraphicsManager {
public:
LinuxmotoSdlGraphicsManager(SdlEventSource *sdlEventSource);
LinuxmotoSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window);
virtual void initSize(uint w, uint h);
virtual void setGraphicsModeIntern();

View file

@ -21,16 +21,14 @@
*/
#if defined(MAEMO)
#include "SDL_syswm.h"
#include "common/scummsys.h"
#include "backends/platform/maemo/maemo.h"
#include "backends/events/maemosdl/maemosdl-events.h"
#include "backends/graphics/maemosdl/maemosdl-graphics.h"
MaemoSdlGraphicsManager::MaemoSdlGraphicsManager(SdlEventSource *sdlEventSource)
: SurfaceSdlGraphicsManager(sdlEventSource) {
MaemoSdlGraphicsManager::MaemoSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window)
: SurfaceSdlGraphicsManager(sdlEventSource, window) {
}
bool MaemoSdlGraphicsManager::loadGFXMode() {

View file

@ -29,7 +29,7 @@
class MaemoSdlGraphicsManager : public SurfaceSdlGraphicsManager {
public:
MaemoSdlGraphicsManager(SdlEventSource *sdlEventSource);
MaemoSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window);
protected:
virtual bool loadGFXMode();

View file

@ -49,9 +49,9 @@ OpenGLGraphicsManager::OpenGLGraphicsManager()
_displayWidth(0), _displayHeight(0), _defaultFormat(), _defaultFormatAlpha(),
_gameScreen(nullptr), _gameScreenShakeOffset(0), _overlay(nullptr),
_overlayVisible(false), _cursor(nullptr),
_cursorX(0), _cursorY(0), _cursorHotspotX(0), _cursorHotspotY(0), _cursorHotspotXScaled(0),
_cursorHotspotYScaled(0), _cursorWidthScaled(0), _cursorHeightScaled(0), _cursorKeyColor(0),
_cursorVisible(false), _cursorDontScale(false), _cursorPaletteEnabled(false)
_cursorX(0), _cursorY(0), _cursorDisplayX(0),_cursorDisplayY(0), _cursorHotspotX(0), _cursorHotspotY(0),
_cursorHotspotXScaled(0), _cursorHotspotYScaled(0), _cursorWidthScaled(0), _cursorHeightScaled(0),
_cursorKeyColor(0), _cursorVisible(false), _cursorDontScale(false), _cursorPaletteEnabled(false)
#ifdef USE_OSD
, _osdAlpha(0), _osdFadeStartTime(0), _osd(nullptr)
#endif
@ -351,7 +351,7 @@ void OpenGLGraphicsManager::updateScreen() {
return;
}
// Clear the screen buffer
// Clear the screen buffer.
GLCALL(glClear(GL_COLOR_BUFFER_BIT));
const GLfloat shakeOffset = _gameScreenShakeOffset * (GLfloat)_displayHeight / _gameScreen->getHeight();
@ -370,12 +370,42 @@ void OpenGLGraphicsManager::updateScreen() {
// visible.
const GLfloat cursorOffset = _overlayVisible ? 0 : shakeOffset;
_cursor->draw(_cursorX - _cursorHotspotXScaled, _cursorY - _cursorHotspotYScaled + cursorOffset,
_cursor->draw(_cursorDisplayX - _cursorHotspotXScaled,
_cursorDisplayY - _cursorHotspotYScaled + cursorOffset,
_cursorWidthScaled, _cursorHeightScaled);
}
// Fourth step: Draw black borders around the game screen when no overlay
// is visible. This makes sure that the mouse cursor etc. is only drawn
// in the actual game screen area in this case.
if (!_overlayVisible) {
GLCALL(glColor4f(0.0f, 0.0f, 0.0f, 1.0f));
GLCALL(glDisable(GL_TEXTURE_2D));
GLCALL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
// Top border.
drawRect(0, 0, _outputScreenWidth, _displayY);
// Left border.
drawRect(0, 0, _displayX, _outputScreenHeight);
// Bottom border.
const int y = _displayY + _displayHeight;
drawRect(0, y, _outputScreenWidth, _outputScreenHeight - y);
// Right border.
const int x = _displayX + _displayWidth;
drawRect(x, 0, _outputScreenWidth - x, _outputScreenHeight);
GLCALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY));
GLCALL(glEnable(GL_TEXTURE_2D));
GLCALL(glColor4f(1.0f, 1.0f, 1.0f, 1.0f));
}
#ifdef USE_OSD
// Fourth step: Draw the OSD.
// Fifth step: Draw the OSD.
if (_osdAlpha > 0) {
Common::StackLock lock(_osdMutex);
@ -435,10 +465,16 @@ int16 OpenGLGraphicsManager::getOverlayHeight() {
void OpenGLGraphicsManager::showOverlay() {
_overlayVisible = true;
// Update cursor position.
setMousePosition(_cursorX, _cursorY);
}
void OpenGLGraphicsManager::hideOverlay() {
_overlayVisible = false;
// Update cursor position.
setMousePosition(_cursorX, _cursorY);
}
Graphics::PixelFormat OpenGLGraphicsManager::getOverlayFormat() const {
@ -892,8 +928,8 @@ void OpenGLGraphicsManager::adjustMousePosition(int16 &x, int16 &y) {
const int16 width = _gameScreen->getWidth();
const int16 height = _gameScreen->getHeight();
x = (x * width) / _displayWidth;
y = (y * height) / _displayHeight;
x = (x * width) / (int)_displayWidth;
y = (y * height) / (int)_displayHeight;
// Make sure we only supply valid coordinates.
x = CLIP<int16>(x, 0, width - 1);
@ -901,6 +937,19 @@ void OpenGLGraphicsManager::adjustMousePosition(int16 &x, int16 &y) {
}
}
void OpenGLGraphicsManager::setMousePosition(int x, int y) {
_cursorX = x;
_cursorY = y;
if (_overlayVisible) {
_cursorDisplayX = x;
_cursorDisplayY = y;
} else {
_cursorDisplayX = CLIP<int>(x, _displayX, _displayX + _displayWidth - 1);
_cursorDisplayY = CLIP<int>(y, _displayY, _displayY + _displayHeight - 1);
}
}
Texture *OpenGLGraphicsManager::createTexture(const Graphics::PixelFormat &format, bool wantAlpha) {
GLenum glIntFormat, glFormat, glType;
if (format.bytesPerPixel == 1) {
@ -1046,6 +1095,9 @@ void OpenGLGraphicsManager::recalculateDisplayArea() {
// We center the screen in the middle for now.
_displayX = (_outputScreenWidth - _displayWidth ) / 2;
_displayY = (_outputScreenHeight - _displayHeight) / 2;
// Update the cursor position to adjust for new display area.
setMousePosition(_cursorX, _cursorY);
}
void OpenGLGraphicsManager::updateCursorPalette() {
@ -1163,4 +1215,20 @@ void OpenGLGraphicsManager::saveScreenshot(const Common::String &filename) const
delete[] pixels;
}
void OpenGLGraphicsManager::drawRect(GLfloat x, GLfloat y, GLfloat w, GLfloat h) {
if (w < 0 || h < 0) {
return;
}
const GLfloat vertices[4*2] = {
x, y,
x + w, y,
x, y + h,
x + w, y + h
};
GLCALL(glVertexPointer(2, GL_FLOAT, 0, vertices));
GLCALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
}
} // End of namespace OpenGL

View file

@ -155,7 +155,7 @@ protected:
* @param x X coordinate in physical coordinates.
* @param y Y coordinate in physical coordinates.
*/
void setMousePosition(int x, int y) { _cursorX = x; _cursorY = y; }
void setMousePosition(int x, int y);
/**
* Query the mouse position in physical coordinates.
@ -393,6 +393,16 @@ private:
*/
int _cursorY;
/**
* X coordinate used for drawing the cursor.
*/
int _cursorDisplayX;
/**
* Y coordinate used for drawing the cursor.
*/
int _cursorDisplayY;
/**
* The X offset for the cursor hotspot in unscaled coordinates.
*/
@ -454,6 +464,11 @@ private:
*/
byte _cursorPalette[3 * 256];
/**
* Draws a rectangle
*/
void drawRect(GLfloat x, GLfloat y, GLfloat w, GLfloat h);
#ifdef USE_OSD
//
// OSD

View file

@ -28,8 +28,13 @@
#include "common/translation.h"
#endif
OpenGLSdlGraphicsManager::OpenGLSdlGraphicsManager(uint desktopWidth, uint desktopHeight, SdlEventSource *eventSource)
: SdlGraphicsManager(eventSource), _lastVideoModeLoad(0), _hwScreen(nullptr), _lastRequestedWidth(0), _lastRequestedHeight(0),
OpenGLSdlGraphicsManager::OpenGLSdlGraphicsManager(uint desktopWidth, uint desktopHeight, SdlEventSource *eventSource, SdlWindow *window)
: SdlGraphicsManager(eventSource, window), _lastRequestedHeight(0),
#if SDL_VERSION_ATLEAST(2, 0, 0)
_glContext(),
#else
_lastVideoModeLoad(0), _hwScreen(nullptr),
#endif
_graphicsScale(2), _ignoreLoadVideoMode(false), _gotResize(false), _wantsFullScreen(false), _ignoreResizeEvents(0),
_desiredFullscreenWidth(0), _desiredFullscreenHeight(0) {
// Setup OpenGL attributes for SDL
@ -40,16 +45,40 @@ OpenGLSdlGraphicsManager::OpenGLSdlGraphicsManager(uint desktopWidth, uint deskt
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
// Retrieve a list of working fullscreen modes
#if SDL_VERSION_ATLEAST(2, 0, 0)
const int numModes = SDL_GetNumDisplayModes(0);
for (int i = 0; i < numModes; ++i) {
SDL_DisplayMode mode;
if (SDL_GetDisplayMode(0, i, &mode)) {
continue;
}
_fullscreenVideoModes.push_back(VideoMode(mode.w, mode.h));
}
#else
const SDL_Rect *const *availableModes = SDL_ListModes(NULL, SDL_OPENGL | SDL_FULLSCREEN);
if (availableModes != (void *)-1) {
// TODO: NULL means that there are no fullscreen modes supported. We
// should probably use this information and disable any fullscreen support
// in this case.
if (availableModes != NULL && availableModes != (void *)-1) {
for (;*availableModes; ++availableModes) {
const SDL_Rect *mode = *availableModes;
_fullscreenVideoModes.push_back(VideoMode(mode->w, mode->h));
}
}
#endif
// Sort the modes in ascending order.
Common::sort(_fullscreenVideoModes.begin(), _fullscreenVideoModes.end());
// Sort the modes in ascending order.
Common::sort(_fullscreenVideoModes.begin(), _fullscreenVideoModes.end());
// Strip duplicates in video modes.
for (uint i = 0; i + 1 < _fullscreenVideoModes.size();) {
if (_fullscreenVideoModes[i] == _fullscreenVideoModes[i + 1]) {
_fullscreenVideoModes.remove_at(i);
} else {
++i;
}
}
// In case SDL is fine with every mode we will force the desktop mode.
@ -108,7 +137,7 @@ void OpenGLSdlGraphicsManager::setFeatureState(OSystem::Feature f, bool enable)
case OSystem::kFeatureIconifyWindow:
if (enable) {
SDL_WM_IconifyWindow();
_window->iconifyWindow();
}
break;
@ -120,11 +149,19 @@ void OpenGLSdlGraphicsManager::setFeatureState(OSystem::Feature f, bool enable)
bool OpenGLSdlGraphicsManager::getFeatureState(OSystem::Feature f) {
switch (f) {
case OSystem::kFeatureFullscreenMode:
#if SDL_VERSION_ATLEAST(2, 0, 0)
if (_window) {
return (SDL_GetWindowFlags(_window->getSDLWindow()) & SDL_WINDOW_FULLSCREEN) != 0;
} else {
return _wantsFullScreen;
}
#else
if (_hwScreen) {
return (_hwScreen->flags & SDL_FULLSCREEN) != 0;
} else {
return _wantsFullScreen;
}
#endif
default:
return OpenGLGraphicsManager::getFeatureState(f);
@ -201,13 +238,20 @@ void OpenGLSdlGraphicsManager::updateScreen() {
OpenGLGraphicsManager::updateScreen();
// Swap OpenGL buffers
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_GL_SwapWindow(_window->getSDLWindow());
#else
SDL_GL_SwapBuffers();
#endif
}
void OpenGLSdlGraphicsManager::notifyVideoExpose() {
}
void OpenGLSdlGraphicsManager::notifyResize(const uint width, const uint height) {
#if SDL_VERSION_ATLEAST(2, 0, 0)
setActualScreenSize(width, height);
#else
if (!_ignoreResizeEvents && _hwScreen && !(_hwScreen->flags & SDL_FULLSCREEN)) {
// We save that we handled a resize event here. We need to know this
// so we do not overwrite the users requested window size whenever we
@ -218,6 +262,7 @@ void OpenGLSdlGraphicsManager::notifyResize(const uint width, const uint height)
g_system->quit();
}
}
#endif
}
void OpenGLSdlGraphicsManager::transformMouseCoordinates(Common::Point &point) {
@ -229,7 +274,7 @@ void OpenGLSdlGraphicsManager::notifyMousePos(Common::Point mouse) {
}
void OpenGLSdlGraphicsManager::setInternalMousePosition(int x, int y) {
SDL_WarpMouse(x, y);
_window->warpMouseInWindow(x, y);
}
bool OpenGLSdlGraphicsManager::loadVideoMode(uint requestedWidth, uint requestedHeight, const Graphics::PixelFormat &format) {
@ -300,6 +345,58 @@ bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) {
height = _desiredFullscreenHeight;
}
// This is pretty confusing since RGBA8888 talks about the memory
// layout here. This is a different logical layout depending on
// whether we run on little endian or big endian. However, we can
// only safely assume that RGBA8888 in memory layout is supported.
// Thus, we chose this one.
const Graphics::PixelFormat rgba8888 =
#ifdef SCUMM_LITTLE_ENDIAN
Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24);
#else
Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0);
#endif
#if SDL_VERSION_ATLEAST(2, 0, 0)
if (_glContext) {
notifyContextDestroy();
SDL_GL_DeleteContext(_glContext);
_glContext = nullptr;
}
_window->destroyWindow();
uint32 flags = SDL_WINDOW_OPENGL;
if (_wantsFullScreen) {
flags |= SDL_WINDOW_FULLSCREEN;
} else {
flags |= SDL_WINDOW_RESIZABLE;
}
if (!_window->createWindow(width, height, flags)) {
// We treat fullscreen requests as a "hint" for now. This means in
// case it is not available we simply ignore it.
if (_wantsFullScreen) {
_window->createWindow(width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
}
if (!_window->getSDLWindow()) {
return false;
}
}
_glContext = SDL_GL_CreateContext(_window->getSDLWindow());
if (!_glContext) {
return false;
}
notifyContextCreate(rgba8888, rgba8888);
int actualWidth, actualHeight;
getWindowDimensions(&actualWidth, &actualHeight);
setActualScreenSize(actualWidth, actualHeight);
return true;
#else
// WORKAROUND: Working around infamous SDL bugs when switching
// resolutions too fast. This might cause the event system to supply
// incorrect mouse position events otherwise.
@ -341,17 +438,6 @@ bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) {
_lastVideoModeLoad = SDL_GetTicks();
if (_hwScreen) {
// This is pretty confusing since RGBA8888 talks about the memory
// layout here. This is a different logical layout depending on
// whether we run on little endian or big endian. However, we can
// only safely assume that RGBA8888 in memory layout is supported.
// Thus, we chose this one.
const Graphics::PixelFormat rgba8888 =
#ifdef SCUMM_LITTLE_ENDIAN
Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24);
#else
Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0);
#endif
notifyContextCreate(rgba8888, rgba8888);
setActualScreenSize(_hwScreen->w, _hwScreen->h);
}
@ -363,6 +449,21 @@ bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) {
_ignoreResizeEvents = 10;
return _hwScreen != nullptr;
#endif
}
void OpenGLSdlGraphicsManager::getWindowDimensions(int *width, int *height) {
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_GetWindowSize(_window->getSDLWindow(), width, height);
#else
if (width) {
*width = _hwScreen->w;
}
if (height) {
*height = _hwScreen->h;
}
#endif
}
bool OpenGLSdlGraphicsManager::notifyEvent(const Common::Event &event) {
@ -456,7 +557,9 @@ bool OpenGLSdlGraphicsManager::notifyEvent(const Common::Event &event) {
// Calculate the next scaling setting. We approximate the
// current scale setting in case the user resized the
// window. Then we apply the direction change.
_graphicsScale = MAX<int>(_hwScreen->w / _lastRequestedWidth, _hwScreen->h / _lastRequestedHeight);
int windowWidth = 0, windowHeight = 0;
getWindowDimensions(&windowWidth, &windowHeight);
_graphicsScale = MAX<int>(windowWidth / _lastRequestedWidth, windowHeight / _lastRequestedHeight);
_graphicsScale = MAX<int>(_graphicsScale + direction, 1);
// Since we overwrite a user resize here we reset its
@ -472,7 +575,9 @@ bool OpenGLSdlGraphicsManager::notifyEvent(const Common::Event &event) {
}
#ifdef USE_OSD
const Common::String osdMsg = Common::String::format("Resolution: %dx%d", _hwScreen->w, _hwScreen->h);
int windowWidth = 0, windowHeight = 0;
getWindowDimensions(&windowWidth, &windowHeight);
const Common::String osdMsg = Common::String::format("Resolution: %dx%d", windowWidth, windowHeight);
displayMessageOnOSD(osdMsg.c_str());
#endif

View file

@ -32,7 +32,7 @@
class OpenGLSdlGraphicsManager : public OpenGL::OpenGLGraphicsManager, public SdlGraphicsManager, public Common::EventObserver {
public:
OpenGLSdlGraphicsManager(uint desktopWidth, uint desktopHeight, SdlEventSource *eventSource);
OpenGLSdlGraphicsManager(uint desktopWidth, uint desktopHeight, SdlEventSource *eventSource, SdlWindow *window);
virtual ~OpenGLSdlGraphicsManager();
// GraphicsManager API
@ -68,8 +68,14 @@ protected:
private:
bool setupMode(uint width, uint height);
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_GLContext _glContext;
#else
uint32 _lastVideoModeLoad;
SDL_Surface *_hwScreen;
#endif
void getWindowDimensions(int *width, int *height);
uint _lastRequestedWidth;
uint _lastRequestedHeight;

View file

@ -32,8 +32,8 @@
static SDL_Cursor *hiddenCursor;
OPGraphicsManager::OPGraphicsManager(SdlEventSource *sdlEventSource)
: SurfaceSdlGraphicsManager(sdlEventSource) {
OPGraphicsManager::OPGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window)
: SurfaceSdlGraphicsManager(sdlEventSource, window) {
}
bool OPGraphicsManager::loadGFXMode() {

View file

@ -32,7 +32,7 @@ enum {
class OPGraphicsManager : public SurfaceSdlGraphicsManager {
public:
OPGraphicsManager(SdlEventSource *sdlEventSource);
OPGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window);
bool loadGFXMode();
void unloadGFXMode();

View file

@ -28,8 +28,8 @@
#include "backends/events/samsungtvsdl/samsungtvsdl-events.h"
#include "backends/graphics/samsungtvsdl/samsungtvsdl-graphics.h"
SamsungTVSdlGraphicsManager::SamsungTVSdlGraphicsManager(SdlEventSource *sdlEventSource)
: SurfaceSdlGraphicsManager(sdlEventSource) {
SamsungTVSdlGraphicsManager::SamsungTVSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window)
: SurfaceSdlGraphicsManager(sdlEventSource, window) {
}
bool SamsungTVSdlGraphicsManager::hasFeature(OSystem::Feature f) {

View file

@ -29,7 +29,7 @@
class SamsungTVSdlGraphicsManager : public SurfaceSdlGraphicsManager {
public:
SamsungTVSdlGraphicsManager(SdlEventSource *sdlEventSource);
SamsungTVSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window);
bool hasFeature(OSystem::Feature f);
void setFeatureState(OSystem::Feature f, bool enable);

View file

@ -22,10 +22,12 @@
#include "backends/graphics/sdl/sdl-graphics.h"
#include "backends/platform/sdl/sdl-sys.h"
#include "backends/events/sdl/sdl-events.h"
#include "common/textconsole.h"
SdlGraphicsManager::SdlGraphicsManager(SdlEventSource *source)
: _eventSource(source) {
SdlGraphicsManager::SdlGraphicsManager(SdlEventSource *source, SdlWindow *window)
: _eventSource(source), _window(window) {
}
SdlGraphicsManager::~SdlGraphicsManager() {
@ -38,3 +40,36 @@ void SdlGraphicsManager::activateManager() {
void SdlGraphicsManager::deactivateManager() {
_eventSource->setGraphicsManager(0);
}
SdlGraphicsManager::State SdlGraphicsManager::getState() {
State state;
state.screenWidth = getWidth();
state.screenHeight = getHeight();
state.aspectRatio = getFeatureState(OSystem::kFeatureAspectRatioCorrection);
state.fullscreen = getFeatureState(OSystem::kFeatureFullscreenMode);
state.cursorPalette = getFeatureState(OSystem::kFeatureCursorPalette);
#ifdef USE_RGB_COLOR
state.pixelFormat = getScreenFormat();
#endif
return state;
}
bool SdlGraphicsManager::setState(const State &state) {
beginGFXTransaction();
#ifdef USE_RGB_COLOR
initSize(state.screenWidth, state.screenHeight, &state.pixelFormat);
#else
initSize(state.screenWidth, state.screenHeight, 0);
#endif
setFeatureState(OSystem::kFeatureAspectRatioCorrection, state.aspectRatio);
setFeatureState(OSystem::kFeatureFullscreenMode, state.fullscreen);
setFeatureState(OSystem::kFeatureCursorPalette, state.cursorPalette);
if (endGFXTransaction() != OSystem::kTransactionSuccess) {
return false;
} else {
return true;
}
}

View file

@ -24,6 +24,7 @@
#define BACKENDS_GRAPHICS_SDL_SDLGRAPHICS_H
#include "backends/graphics/graphics.h"
#include "backends/platform/sdl/sdl-window.h"
#include "common/rect.h"
@ -36,7 +37,7 @@ class SdlEventSource;
*/
class SdlGraphicsManager : virtual public GraphicsManager {
public:
SdlGraphicsManager(SdlEventSource *source);
SdlGraphicsManager(SdlEventSource *source, SdlWindow *window);
virtual ~SdlGraphicsManager();
/**
@ -91,8 +92,39 @@ public:
*/
virtual void notifyMousePos(Common::Point mouse) = 0;
/**
* A (subset) of the graphic manager's state. This is used when switching
* between different SDL graphic managers on runtime.
*/
struct State {
int screenWidth, screenHeight;
bool aspectRatio;
bool fullscreen;
bool cursorPalette;
#ifdef USE_RGB_COLOR
Graphics::PixelFormat pixelFormat;
#endif
};
/**
* Queries the current state of the graphic manager.
*/
State getState();
/**
* Setup a basic state of the graphic manager.
*/
bool setState(const State &state);
/**
* Queries the SDL window.
*/
SdlWindow *getWindow() const { return _window; }
protected:
SdlEventSource *_eventSource;
SdlWindow *_window;
};
#endif

View file

@ -116,13 +116,19 @@ static AspectRatio getDesiredAspectRatio() {
}
#endif
SurfaceSdlGraphicsManager::SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSource)
SurfaceSdlGraphicsManager::SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window)
:
SdlGraphicsManager(sdlEventSource),
SdlGraphicsManager(sdlEventSource, window),
#ifdef USE_OSD
_osdSurface(0), _osdAlpha(SDL_ALPHA_TRANSPARENT), _osdFadeStartTime(0),
#endif
_hwscreen(0), _screen(0), _tmpscreen(0),
_hwscreen(0),
#if SDL_VERSION_ATLEAST(2, 0, 0)
_renderer(nullptr), _screenTexture(nullptr),
#else
_originalBitsPerPixel(0),
#endif
_screen(0), _tmpscreen(0),
#ifdef USE_RGB_COLOR
_screenFormat(Graphics::PixelFormat::createFormatCLUT8()),
_cursorFormat(Graphics::PixelFormat::createFormatCLUT8()),
@ -235,7 +241,7 @@ void SurfaceSdlGraphicsManager::setFeatureState(OSystem::Feature f, bool enable)
break;
case OSystem::kFeatureIconifyWindow:
if (enable)
SDL_WM_IconifyWindow();
_window->iconifyWindow();
break;
default:
break;
@ -681,12 +687,22 @@ static void fixupResolutionForAspectRatio(AspectRatio desiredAspectRatio, int &w
const int w = width;
const int h = height;
int bestW = 0, bestH = 0;
uint bestMetric = (uint)-1; // Metric is wasted space
#if SDL_VERSION_ATLEAST(2, 0, 0)
const int numModes = SDL_GetNumDisplayModes(0);
SDL_DisplayMode modeData, *mode = &modeData;
for (int i = 0; i < numModes; ++i) {
if (SDL_GetDisplayMode(0, i, &modeData)) {
continue;
}
#else
SDL_Rect const* const*availableModes = SDL_ListModes(NULL, SDL_FULLSCREEN|SDL_SWSURFACE); //TODO : Maybe specify a pixel format
assert(availableModes);
const SDL_Rect *bestMode = NULL;
uint bestMetric = (uint)-1; // Metric is wasted space
while (const SDL_Rect *mode = *availableModes++) {
#endif
if (mode->w < w)
continue;
if (mode->h < h)
@ -699,15 +715,23 @@ static void fixupResolutionForAspectRatio(AspectRatio desiredAspectRatio, int &w
continue;
bestMetric = metric;
bestMode = mode;
}
bestW = mode->w;
bestH = mode->h;
if (!bestMode) {
// Make editors a bit more happy by having the same amount of closing as
// opening curley braces.
#if SDL_VERSION_ATLEAST(2, 0, 0)
}
#else
}
#endif
if (!bestW || !bestH) {
warning("Unable to enforce the desired aspect ratio");
return;
}
width = bestMode->w;
height = bestMode->h;
width = bestW;
height = bestH;
}
bool SurfaceSdlGraphicsManager::loadGFXMode() {
@ -774,7 +798,15 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() {
_hwscreen = g_eventRec.getSurface(_videoMode.hardwareWidth, _videoMode.hardwareHeight);
} else
#endif
{
{
// Save the original bpp to be able to restore the video mode on unload
#if !SDL_VERSION_ATLEAST(2, 0, 0)
if (_originalBitsPerPixel == 0) {
const SDL_VideoInfo *videoInfo = SDL_GetVideoInfo();
_originalBitsPerPixel = videoInfo->vfmt->BitsPerPixel;
}
#endif
_hwscreen = SDL_SetVideoMode(_videoMode.hardwareWidth, _videoMode.hardwareHeight, 16,
_videoMode.fullscreen ? (SDL_FULLSCREEN|SDL_SWSURFACE) : SDL_SWSURFACE
);
@ -876,6 +908,10 @@ void SurfaceSdlGraphicsManager::unloadGFXMode() {
_screen = NULL;
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
deinitializeRenderer();
#endif
if (_hwscreen) {
SDL_FreeSurface(_hwscreen);
_hwscreen = NULL;
@ -903,6 +939,13 @@ void SurfaceSdlGraphicsManager::unloadGFXMode() {
}
#endif
DestroyScalers();
#if !SDL_VERSION_ATLEAST(2, 0, 0)
// Reset video mode to original
// This will ensure that any new graphic manager will use the initial BPP when listing available modes
if (_originalBitsPerPixel != 0)
SDL_SetVideoMode(_videoMode.screenWidth, _videoMode.screenHeight, _originalBitsPerPixel, _videoMode.fullscreen ? (SDL_FULLSCREEN | SDL_SWSURFACE) : SDL_SWSURFACE);
#endif
}
bool SurfaceSdlGraphicsManager::hotswapGFXMode() {
@ -1443,6 +1486,9 @@ void SurfaceSdlGraphicsManager::setPalette(const byte *colors, uint start, uint
base[i].r = b[0];
base[i].g = b[1];
base[i].b = b[2];
#if SDL_VERSION_ATLEAST(2, 0, 0)
base[i].a = 255;
#endif
}
if (start < _paletteDirtyStart)
@ -1481,6 +1527,9 @@ void SurfaceSdlGraphicsManager::setCursorPalette(const byte *colors, uint start,
base[i].r = b[0];
base[i].g = b[1];
base[i].b = b[2];
#if SDL_VERSION_ATLEAST(2, 0, 0)
base[i].a = 255;
#endif
}
_cursorPaletteDisabled = false;
@ -1710,7 +1759,7 @@ void SurfaceSdlGraphicsManager::warpMouse(int x, int y) {
int y1 = y;
// Don't change actual mouse position, when mouse is outside of our window (in case of windowed mode)
if (!(SDL_GetAppState( ) & SDL_APPMOUSEFOCUS)) {
if (!_window->hasMouseFocus()) {
setMousePos(x, y); // but change game cursor position
return;
}
@ -1720,9 +1769,9 @@ void SurfaceSdlGraphicsManager::warpMouse(int x, int y) {
if (_mouseCurState.x != x || _mouseCurState.y != y) {
if (!_overlayVisible)
SDL_WarpMouse(x * _videoMode.scaleFactor, y1 * _videoMode.scaleFactor);
_window->warpMouseInWindow(x * _videoMode.scaleFactor, y1 * _videoMode.scaleFactor);
else
SDL_WarpMouse(x, y1);
_window->warpMouseInWindow(x, y1);
// SDL_WarpMouse() generates a mouse movement event, so
// setMousePos() would be called eventually. However, the
@ -2317,4 +2366,52 @@ void SurfaceSdlGraphicsManager::notifyMousePos(Common::Point mouse) {
setMousePos(mouse.x, mouse.y);
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
void SurfaceSdlGraphicsManager::deinitializeRenderer() {
SDL_DestroyTexture(_screenTexture);
_screenTexture = nullptr;
SDL_DestroyRenderer(_renderer);
_renderer = nullptr;
_window->destroyWindow();
}
SDL_Surface *SurfaceSdlGraphicsManager::SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags) {
deinitializeRenderer();
if (!_window->createWindow(width, height, (flags & SDL_FULLSCREEN) ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0)) {
return nullptr;
}
_renderer = SDL_CreateRenderer(_window->getSDLWindow(), -1, 0);
if (!_renderer) {
deinitializeRenderer();
return nullptr;
}
_screenTexture = SDL_CreateTexture(_renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, width, height);
if (!_screenTexture) {
deinitializeRenderer();
return nullptr;
}
SDL_Surface *screen = SDL_CreateRGBSurface(0, width, height, 16, 0xF800, 0x7E0, 0x1F, 0);
if (!screen) {
deinitializeRenderer();
return nullptr;
} else {
return screen;
}
}
void SurfaceSdlGraphicsManager::SDL_UpdateRects(SDL_Surface *screen, int numrects, SDL_Rect *rects) {
SDL_UpdateTexture(_screenTexture, nullptr, screen->pixels, screen->pitch);
SDL_RenderClear(_renderer);
SDL_RenderCopy(_renderer, _screenTexture, NULL, NULL);
SDL_RenderPresent(_renderer);
}
#endif // SDL_VERSION_ATLEAST(2, 0, 0)
#endif

View file

@ -77,7 +77,7 @@ public:
*/
class SurfaceSdlGraphicsManager : public SdlGraphicsManager, public Common::EventObserver {
public:
SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSource);
SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window);
virtual ~SurfaceSdlGraphicsManager();
virtual void activateManager();
@ -166,6 +166,17 @@ protected:
/** Hardware screen */
SDL_Surface *_hwscreen;
#if SDL_VERSION_ATLEAST(2, 0, 0)
/* SDL2 features a different API for 2D graphics. We create a wrapper
* around this API to keep the code paths as close as possible. */
SDL_Renderer *_renderer;
SDL_Texture *_screenTexture;
void deinitializeRenderer();
SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags);
void SDL_UpdateRects(SDL_Surface *screen, int numrects, SDL_Rect *rects);
#endif
/** Unseen game screen */
SDL_Surface *_screen;
#ifdef USE_RGB_COLOR
@ -225,6 +236,9 @@ protected:
};
VideoState _videoMode, _oldVideoMode;
// Original BPP to restore the video mode on unload
uint8 _originalBitsPerPixel;
/** Force full redraw on next updateScreen */
bool _forceFull;

View file

@ -27,8 +27,8 @@
#include "backends/graphics/symbiansdl/symbiansdl-graphics.h"
#include "backends/platform/symbian/src/SymbianActions.h"
SymbianSdlGraphicsManager::SymbianSdlGraphicsManager(SdlEventSource *sdlEventSource)
: SurfaceSdlGraphicsManager(sdlEventSource) {
SymbianSdlGraphicsManager::SymbianSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window)
: SurfaceSdlGraphicsManager(sdlEventSource, window) {
}
int SymbianSdlGraphicsManager::getDefaultGraphicsMode() const {

View file

@ -27,7 +27,7 @@
class SymbianSdlGraphicsManager : public SurfaceSdlGraphicsManager {
public:
SymbianSdlGraphicsManager(SdlEventSource *sdlEventSource);
SymbianSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window);
public:
virtual bool hasFeature(OSystem::Feature f);

View file

@ -42,8 +42,8 @@
#include "backends/platform/wince/CEScaler.h"
#include "backends/platform/wince/CEgui/ItemAction.h"
WINCESdlGraphicsManager::WINCESdlGraphicsManager(SdlEventSource *sdlEventSource)
: SurfaceSdlGraphicsManager(sdlEventSource),
WINCESdlGraphicsManager::WINCESdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window)
: SurfaceSdlGraphicsManager(sdlEventSource, window),
_panelInitialized(false), _noDoubleTapRMB(false), _noDoubleTapPT(false),
_toolbarHighDrawn(false), _newOrientation(0), _orientationLandscape(0),
_panelVisible(true), _saveActiveToolbar(NAME_MAIN_PANEL), _panelStateForced(false),

View file

@ -41,7 +41,7 @@ extern bool _hasSmartphoneResolution;
class WINCESdlGraphicsManager : public SurfaceSdlGraphicsManager {
public:
WINCESdlGraphicsManager(SdlEventSource *sdlEventSource);
WINCESdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window);
const OSystem::GraphicsMode *getSupportedGraphicsModes() const;
void initSize(uint w, uint h, const Graphics::PixelFormat *format = NULL);

View file

@ -93,10 +93,12 @@ void Log::print(const char *message, const bool printTime) {
void Log::printTimeStamp() {
TimeDate date;
int curMonth;
_system->getTimeAndDate(date);
curMonth = date.tm_mon + 1; // month is base 0, we need base 1 (1 = january and so on)
_stream->writeString(Common::String::format("[%d-%02d-%02d %02d:%02d:%02d] ",
date.tm_year + 1900, date.tm_mon, date.tm_mday,
date.tm_year + 1900, curMonth, date.tm_mday,
date.tm_hour, date.tm_min, date.tm_sec));
}

View file

@ -53,7 +53,11 @@ void DoubleBufferSDLMixerManager::startAudio() {
_soundThreadIsRunning = true;
// Finally start the thread
#if SDL_VERSION_ATLEAST(2, 0, 0)
_soundThread = SDL_CreateThread(mixerProducerThreadEntry, "ScummVM Double Buffer Mixer", this);
#else
_soundThread = SDL_CreateThread(mixerProducerThreadEntry, this);
#endif
SdlMixerManager::startAudio();
}

View file

@ -57,10 +57,14 @@ void SdlMixerManager::init() {
error("Could not initialize SDL: %s", SDL_GetError());
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
const char *sdlDriverName = SDL_GetCurrentAudioDriver();
#else
const int maxNameLen = 20;
char sdlDriverName[maxNameLen];
sdlDriverName[0] = '\0';
SDL_AudioDriverName(sdlDriverName, maxNameLen);
#endif
debug(1, "Using SDL Audio Driver \"%s\"", sdlDriverName);
// Get the desired audio specs

View file

@ -74,9 +74,11 @@ MODULE_OBJS += \
# SDL 1.3 removed audio CD support
ifndef USE_SDL13
ifndef USE_SDL2
MODULE_OBJS += \
audiocd/sdl/sdl-audiocd.o
endif
endif
ifdef USE_OPENGL
MODULE_OBJS += \

View file

@ -396,12 +396,6 @@ void OSystem_Android::initBackend() {
EventsBaseBackend::initBackend();
}
void OSystem_Android::addPluginDirectories(Common::FSList &dirs) const {
ENTER();
JNI::getPluginDirectories(dirs);
}
bool OSystem_Android::hasFeature(Feature f) {
return (f == kFeatureFullscreenMode ||
f == kFeatureAspectRatioCorrection ||
@ -600,10 +594,4 @@ Common::String OSystem_Android::getSystemProperty(const char *name) const {
return Common::String(value, len);
}
#ifdef DYNAMIC_MODULES
void AndroidPluginProvider::addCustomDirectories(Common::FSList &dirs) const {
((OSystem_Android *)g_system)->addPluginDirectories(dirs);
}
#endif
#endif

View file

@ -96,13 +96,6 @@ extern void checkGlError(const char *expr, const char *file, int line);
#define GLTHREADCHECK do { } while (false)
#endif
#ifdef DYNAMIC_MODULES
class AndroidPluginProvider : public POSIXPluginProvider {
protected:
virtual void addCustomDirectories(Common::FSList &dirs) const;
};
#endif
class OSystem_Android : public EventsBaseBackend, public PaletteManager {
private:
// passed from the dark side
@ -177,7 +170,6 @@ public:
virtual ~OSystem_Android();
virtual void initBackend();
void addPluginDirectories(Common::FSList &dirs) const;
void enableZoning(bool enable) { _enable_zoning = enable; }
virtual bool hasFeature(Feature f);

View file

@ -2,200 +2,106 @@
# These must be incremented for each market upload
ANDROID_VERSIONCODE = 6
ANDROID_PLUGIN_VERSIONCODE = 6
JAVA_FILES = \
ScummVM.java \
ScummVMEvents.java \
ScummVMEventsHoneycomb.java \
ScummVMApplication.java \
ScummVMActivity.java \
EditableSurfaceView.java \
MouseHelper.java \
Unpacker.java
ANDROID_TARGET_VERSION = 14
JAVA_FILES_PLUGIN = \
PluginProvider.java
JAVA_FILES_GEN = \
Manifest.java \
R.java
NDK_BUILD = $(ANDROID_NDK)/ndk-build APP_ABI=$(ABI)
SDK_ANDROID = $(ANDROID_SDK)/tools/android
PATH_DIST = $(srcdir)/dists/android
PATH_RESOURCES = $(PATH_DIST)/res
PORT_DISTFILES = $(PATH_DIST)/README.Android
DIST_JAVA_SRC_DIR = $(srcdir)/backends/platform/android/org
RESOURCES = \
$(PATH_RESOURCES)/values/strings.xml \
$(PATH_RESOURCES)/values/margins.xml \
$(PATH_RESOURCES)/values-television/margins.xml \
$(PATH_RESOURCES)/layout/main.xml \
$(PATH_RESOURCES)/layout/splash.xml \
$(PATH_RESOURCES)/drawable/gradient.xml \
$(PATH_RESOURCES)/drawable/scummvm.png \
$(PATH_RESOURCES)/drawable/scummvm_big.png \
$(PATH_RESOURCES)/drawable-xhdpi/ouya_icon.png
$(PATH_BUILD_RES)/values/strings.xml \
$(PATH_BUILD_RES)/values-television/margins.xml \
$(PATH_BUILD_RES)/layout/main.xml \
$(PATH_BUILD_RES)/drawable/scummvm.png \
$(PATH_BUILD_RES)/drawable/scummvm_big.png \
$(PATH_BUILD_RES)/drawable-xhdpi/ouya_icon.png
PLUGIN_RESOURCES = \
$(PATH_RESOURCES)/values/strings.xml \
$(PATH_RESOURCES)/drawable/scummvm.png
# FIXME: find/mark plugin entry points and add all this back again:
#LDFLAGS += -Wl,--gc-sections
#CXXFLAGS += -ffunction-sections -fdata-sections -fvisibility=hidden -fvisibility-inlines-hidden
AAPT = $(ANDROID_SDK)/$(ANDROID_BTOOLS)/aapt
ADB = $(ANDROID_SDK)/platform-tools/adb
DX = $(ANDROID_SDK)/$(ANDROID_BTOOLS)/dx
APKBUILDER = java -Xmx128M -classpath $(ANDROID_SDK)/tools/lib/sdklib.jar com.android.sdklib.build.ApkBuilderMain
JAVAC ?= javac
JAVACFLAGS = -source 1.5 -target 1.5
ANDROID_JAR = $(ANDROID_SDK)/platforms/android-14/android.jar
DIST_ANDROID_MK = $(PATH_DIST)/jni/Android.mk
DIST_BUILD_XML = $(PATH_DIST)/custom_rules.xml
PATH_BUILD = ./build.tmp
PATH_BUILD_ASSETS = $(PATH_BUILD)/assets
PATH_BUILD_CLASSES_MAIN_TOP = $(PATH_BUILD)/classes.main
PATH_BUILD_CLASSES_PLUGIN_TOP = $(PATH_BUILD)/classes.plugin
PATH_STAGE_PREFIX = build.stage
PATH_STAGE_MAIN = $(PATH_STAGE_PREFIX).main
PATH_REL = org/scummvm/scummvm
PATH_SRC_TOP = $(srcdir)/backends/platform/android
PATH_SRC = $(PATH_SRC_TOP)/$(PATH_REL)
PATH_GEN_TOP = $(PATH_BUILD)/java
PATH_GEN = $(PATH_GEN_TOP)/$(PATH_REL)
PATH_CLASSES_MAIN = $(PATH_BUILD_CLASSES_MAIN_TOP)/$(PATH_REL)
PATH_CLASSES_PLUGIN = $(PATH_BUILD_CLASSES_PLUGIN_TOP)/$(PATH_REL)
PATH_BUILD_RES = $(PATH_BUILD)/res
PATH_BUILD_LIBSCUMMVM = $(PATH_BUILD)/lib/$(ABI)/libscummvm.so
FILE_MANIFEST_SRC = $(srcdir)/dists/android/AndroidManifest.xml
FILE_MANIFEST = $(PATH_BUILD)/AndroidManifest.xml
FILE_DEX = $(PATH_BUILD)/classes.dex
FILE_DEX_PLUGIN = $(PATH_BUILD)/plugins/classes.dex
FILE_RESOURCES = resources.ap_
FILE_RESOURCES_MAIN = $(PATH_BUILD)/$(FILE_RESOURCES)
SRC_GEN = $(addprefix $(PATH_GEN)/, $(JAVA_FILES_GEN))
APK_MAIN = ScummVM-debug.apk
APK_MAIN_RELEASE = ScummVM-release-unsigned.apk
CLASSES_MAIN = $(addprefix $(PATH_CLASSES_MAIN)/, $(JAVA_FILES:%.java=%.class))
CLASSES_GEN = $(addprefix $(PATH_CLASSES_MAIN)/, $(JAVA_FILES_GEN:%.java=%.class))
CLASSES_PLUGIN = $(addprefix $(PATH_CLASSES_PLUGIN)/, $(JAVA_FILES_PLUGIN:%.java=%.class))
APK_MAIN = scummvm.apk
APK_PLUGINS = $(patsubst plugins/lib%.so, scummvm-engine-%.apk, $(PLUGINS))
$(FILE_MANIFEST): $(FILE_MANIFEST_SRC)
$(FILE_MANIFEST): $(FILE_MANIFEST_SRC) | $(PATH_BUILD)
@$(MKDIR) -p $(@D)
sed "s/@ANDROID_VERSIONCODE@/$(ANDROID_VERSIONCODE)/" < $< > $@
$(SRC_GEN): $(FILE_MANIFEST) $(filter %.xml,$(RESOURCES)) $(ANDROID_JAR)
@$(MKDIR) -p $(PATH_GEN_TOP)
$(AAPT) package -m -J $(PATH_GEN_TOP) -M $< -S $(PATH_RESOURCES) -I $(ANDROID_JAR)
$(PATH_CLASSES_MAIN)/%.class: $(PATH_GEN)/%.java $(SRC_GEN)
@$(MKDIR) -p $(@D)
$(JAVAC) $(JAVACFLAGS) -cp $(PATH_SRC_TOP) -d $(PATH_BUILD_CLASSES_MAIN_TOP) -bootclasspath $(ANDROID_JAR) $<
$(PATH_CLASSES_MAIN)/%.class: $(PATH_SRC)/%.java $(SRC_GEN)
@$(MKDIR) -p $(@D)
$(JAVAC) $(JAVACFLAGS) -cp $(PATH_SRC_TOP):$(PATH_GEN_TOP) -d $(PATH_BUILD_CLASSES_MAIN_TOP) -bootclasspath $(ANDROID_JAR) $<
$(PATH_CLASSES_PLUGIN)/%.class: $(PATH_SRC)/%.java
@$(MKDIR) -p $(@D)
$(JAVAC) $(JAVACFLAGS) -cp $(PATH_SRC_TOP) -d $(PATH_BUILD_CLASSES_PLUGIN_TOP) -bootclasspath $(ANDROID_JAR) $<
$(FILE_DEX): $(CLASSES_MAIN) $(CLASSES_GEN)
$(DX) --dex --output=$@ $(PATH_BUILD_CLASSES_MAIN_TOP)
$(FILE_DEX_PLUGIN): $(CLASSES_PLUGIN)
@$(MKDIR) -p $(@D)
$(DX) --dex --output=$@ $(PATH_BUILD_CLASSES_PLUGIN_TOP)
$(PATH_BUILD)/%/AndroidManifest.xml: $(PATH_DIST)/mkplugin.sh $(srcdir)/configure $(PATH_DIST)/plugin-manifest.xml
@$(MKDIR) -p $(@D)
$(PATH_DIST)/mkplugin.sh $(srcdir)/configure $* $(PATH_DIST)/plugin-manifest.xml $(ANDROID_PLUGIN_VERSIONCODE) $@
$(PATH_STAGE_PREFIX).%/res/values/strings.xml: $(PATH_DIST)/mkplugin.sh $(srcdir)/configure $(PATH_DIST)/plugin-manifest.xml
@$(MKDIR) -p $(@D)
$(PATH_DIST)/mkplugin.sh $(srcdir)/configure $* $(PATH_DIST)/plugin-strings.xml $(ANDROID_PLUGIN_VERSIONCODE) $@
$(PATH_STAGE_PREFIX).%/res/drawable/scummvm.png: $(PATH_RESOURCES)/drawable/scummvm.png
$(PATH_BUILD)/res/%: $(PATH_DIST)/res/% | $(PATH_BUILD)
@$(MKDIR) -p $(@D)
$(CP) $< $@
$(FILE_RESOURCES_MAIN): $(FILE_MANIFEST) $(RESOURCES) $(ANDROID_JAR) $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA)
$(PATH_BUILD)/libs/%: $(PATH_DIST)/libs/% | $(PATH_BUILD)
@$(MKDIR) -p $(@D)
$(CP) $< $@
$(PATH_BUILD_ASSETS): $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_SHADERS) $(DIST_BUILD_XML) | $(PATH_BUILD)
$(INSTALL) -d $(PATH_BUILD_ASSETS)
$(INSTALL) -c -m 644 $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(PATH_BUILD_ASSETS)/
work_dir=`pwd`; \
for i in $(PATH_BUILD_ASSETS)/*.zip; do \
echo "recompress $$i"; \
cd "$$work_dir"; \
$(RM) -rf $(PATH_BUILD_ASSETS)/tmp; \
$(MKDIR) $(PATH_BUILD_ASSETS)/tmp; \
unzip -q $$i -d $(PATH_BUILD_ASSETS)/tmp; \
cd $(PATH_BUILD_ASSETS)/tmp; \
zip -r ../`basename $$i` *; \
done
@$(RM) -rf $(PATH_BUILD_ASSETS)/tmp
$(AAPT) package -f -0 zip -M $< -S $(PATH_RESOURCES) -A $(PATH_BUILD_ASSETS) -I $(ANDROID_JAR) -F $@
$(INSTALL) -d $(PATH_BUILD)/jni
$(INSTALL) -c -m 644 $(DIST_ANDROID_MK) $(PATH_BUILD)/jni
$(INSTALL) -c -m 644 $(DIST_BUILD_XML) $(PATH_BUILD)
$(PATH_BUILD)/%/$(FILE_RESOURCES): $(PATH_BUILD)/%/AndroidManifest.xml $(PATH_STAGE_PREFIX).%/res/values/strings.xml $(PATH_STAGE_PREFIX).%/res/drawable/scummvm.png plugins/lib%.so $(ANDROID_JAR)
$(AAPT) package -f -M $< -S $(PATH_STAGE_PREFIX).$*/res -I $(ANDROID_JAR) -F $@
$(PATH_BUILD): $(DIST_ANDROID_MK)
$(MKDIR) -p $(PATH_BUILD) $(PATH_BUILD)/res
$(MKDIR) -p $(PATH_BUILD)/libs
# Package installer won't delete old libscummvm.so on upgrade so
# replace it with a zero size file
$(APK_MAIN): $(EXECUTABLE) $(FILE_RESOURCES_MAIN) $(FILE_DEX)
$(INSTALL) -d $(PATH_STAGE_MAIN)/common/lib/armeabi
touch $(PATH_STAGE_MAIN)/common/lib/armeabi/libscummvm.so
$(INSTALL) -d $(PATH_STAGE_MAIN)/common/mylib/armeabi
$(INSTALL) -c -m 644 libscummvm.so $(PATH_STAGE_MAIN)/common/mylib/armeabi/
$(STRIP) $(PATH_STAGE_MAIN)/common/mylib/armeabi/libscummvm.so
$(APKBUILDER) $@ -z $(FILE_RESOURCES_MAIN) -f $(FILE_DEX) -rf $(PATH_STAGE_MAIN)/common || { $(RM) $@; exit 1; }
$(PATH_BUILD_LIBSCUMMVM): libscummvm.so | $(PATH_BUILD)
$(INSTALL) -c -m 644 libscummvm.so $(PATH_BUILD)
$(STRIP) $(PATH_BUILD)/libscummvm.so
cd $(PATH_BUILD); $(NDK_BUILD)
scummvm-engine-%.apk: plugins/lib%.so $(PATH_BUILD)/%/$(FILE_RESOURCES) $(FILE_DEX_PLUGIN)
$(INSTALL) -d $(PATH_STAGE_PREFIX).$*/apk/mylib/armeabi/
$(INSTALL) -c -m 644 plugins/lib$*.so $(PATH_STAGE_PREFIX).$*/apk/mylib/armeabi/
$(STRIP) $(PATH_STAGE_PREFIX).$*/apk/mylib/armeabi/lib$*.so
$(APKBUILDER) $@ -z $(PATH_BUILD)/$*/$(FILE_RESOURCES) -f $(FILE_DEX_PLUGIN) -rf $(PATH_STAGE_PREFIX).$*/apk || { $(RM) $@; exit 1; }
$(PATH_BUILD_RES): $(RESOURCES) | $(PATH_BUILD)
all: $(APK_MAIN) $(APK_PLUGINS)
setupapk: $(FILE_MANIFEST) $(PATH_BUILD_RES) $(PATH_BUILD_ASSETS) $(PATH_BUILD_LIBSCUMMVM) | $(PATH_BUILD)
$(SDK_ANDROID) update project -p $(PATH_BUILD) -t android-$(ANDROID_TARGET_VERSION) -n ScummVM
$(APK_MAIN): setupapk | $(PATH_BUILD)
(cd $(PATH_BUILD); ant debug -Dsource.dir="$(realpath $(DIST_JAVA_SRC_DIR))")
$(CP) $(PATH_BUILD)/bin/ScummVM-debug.apk $@
$(APK_MAIN_RELEASE): setupapk | $(PATH_BUILD)
(cd $(PATH_BUILD); ant release -Dsource.dir="$(realpath $(DIST_JAVA_SRC_DIR))")
$(CP) $(PATH_BUILD)/bin/ScummVM-release-unsigned.apk $@
all: $(APK_MAIN)
clean: androidclean
androidclean:
@$(RM) -rf $(PATH_BUILD) $(PATH_STAGE_PREFIX).* *.apk release
@$(RM) -rf $(PATH_BUILD) *.apk release
# remove debugging signature
release/%.apk: %.apk
@$(MKDIR) -p $(@D)
@$(RM) $@
$(CP) $< $@.tmp
zip -d $@.tmp META-INF/\*
jarsigner $(JARSIGNER_FLAGS) $@.tmp release
zipalign 4 $@.tmp $@
$(RM) $@.tmp
androidrelease: $(addprefix release/, $(APK_MAIN) $(APK_PLUGINS))
androidrelease: $(APK_MAIN_RELEASE)
androidtestmain: $(APK_MAIN)
$(ADB) install -r $(APK_MAIN)
$(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.scummvm.scummvm/.Unpacker
$(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.scummvm.scummvm/.ScummVMActivity
androidtest: $(APK_MAIN) $(APK_PLUGINS)
androidtest: $(APK_MAIN)
@set -e; for apk in $^; do \
$(ADB) install -r $$apk; \
done
$(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.scummvm.scummvm/.Unpacker
$(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.scummvm.scummvm/.ScummVMActivity
# used by buildbot!
androiddistdebug: all
$(MKDIR) debug
$(CP) $(APK_MAIN) $(APK_PLUGINS) debug/
$(CP) $(APK_MAIN) debug/
for i in $(DIST_FILES_DOCS) $(PORT_DISTFILES); do \
sed 's/$$/\r/' < $$i > debug/`basename $$i`.txt; \
done
.PHONY: androidrelease androidtest
.PHONY: androidrelease androidtest $(PATH_BUILD_SRC)

View file

@ -79,7 +79,6 @@ jmethodID JNI::_MID_displayMessageOnOSD = 0;
jmethodID JNI::_MID_setWindowCaption = 0;
jmethodID JNI::_MID_showVirtualKeyboard = 0;
jmethodID JNI::_MID_getSysArchives = 0;
jmethodID JNI::_MID_getPluginDirectories = 0;
jmethodID JNI::_MID_initSurface = 0;
jmethodID JNI::_MID_deinitSurface = 0;
@ -293,46 +292,6 @@ void JNI::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) {
}
}
void JNI::getPluginDirectories(Common::FSList &dirs) {
JNIEnv *env = JNI::getEnv();
jobjectArray array =
(jobjectArray)env->CallObjectMethod(_jobj, _MID_getPluginDirectories);
if (env->ExceptionCheck()) {
LOGE("Error finding plugin directories");
env->ExceptionDescribe();
env->ExceptionClear();
return;
}
jsize size = env->GetArrayLength(array);
for (jsize i = 0; i < size; ++i) {
jstring path_obj = (jstring)env->GetObjectArrayElement(array, i);
if (path_obj == 0)
continue;
const char *path = env->GetStringUTFChars(path_obj, 0);
if (path == 0) {
LOGE("Error getting string characters from plugin directory");
env->ExceptionClear();
env->DeleteLocalRef(path_obj);
continue;
}
dirs.push_back(Common::FSNode(path));
env->ReleaseStringUTFChars(path_obj, path);
env->DeleteLocalRef(path_obj);
}
}
bool JNI::initSurface() {
JNIEnv *env = JNI::getEnv();
@ -454,7 +413,6 @@ void JNI::create(JNIEnv *env, jobject self, jobject asset_manager,
FIND_METHOD(, displayMessageOnOSD, "(Ljava/lang/String;)V");
FIND_METHOD(, showVirtualKeyboard, "(Z)V");
FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;");
FIND_METHOD(, getPluginDirectories, "()[Ljava/lang/String;");
FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;");
FIND_METHOD(, deinitSurface, "()V");
@ -543,10 +501,6 @@ jint JNI::main(JNIEnv *env, jobject self, jobjectArray args) {
env->DeleteLocalRef(arg);
}
#ifdef DYNAMIC_MODULES
PluginManager::instance().addPluginProvider(new AndroidPluginProvider());
#endif
LOGI("Entering scummvm_main with %d args", argc);
res = scummvm_main(argc, argv);

View file

@ -55,7 +55,6 @@ public:
static void setReadyForEvents(bool ready);
static void getPluginDirectories(Common::FSList &dirs);
static void setWindowCaption(const char *caption);
static void getDPI(float *values);
static void displayMessageOnOSD(const char *msg);
@ -93,7 +92,6 @@ private:
static jmethodID _MID_setWindowCaption;
static jmethodID _MID_showVirtualKeyboard;
static jmethodID _MID_getSysArchives;
static jmethodID _MID_getPluginDirectories;
static jmethodID _MID_initSurface;
static jmethodID _MID_deinitSurface;

View file

@ -1,63 +0,0 @@
package org.scummvm.scummvm;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import java.util.ArrayList;
public class PluginProvider extends BroadcastReceiver {
private final static String LOG_TAG = "ScummVM";
public final static String META_UNPACK_LIB =
"org.scummvm.scummvm.meta.UNPACK_LIB";
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(ScummVMApplication.ACTION_PLUGIN_QUERY))
return;
Bundle extras = getResultExtras(true);
final ActivityInfo info;
final PackageInfo pinfo;
try {
info = context.getPackageManager()
.getReceiverInfo(new ComponentName(context, this.getClass()),
PackageManager.GET_META_DATA);
pinfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Error finding my own info?", e);
return;
}
String host_version = extras.getString(ScummVMApplication.EXTRA_VERSION);
if (!pinfo.versionName.equals(host_version)) {
Log.e(LOG_TAG, "Plugin version " + pinfo.versionName + " is not equal to ScummVM version " + host_version);
return;
}
String mylib = info.metaData.getString(META_UNPACK_LIB);
if (mylib != null) {
ArrayList<String> all_libs =
extras.getStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS);
all_libs.add(new Uri.Builder()
.scheme("plugin")
.authority(context.getPackageName())
.path(mylib)
.toString());
extras.putStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS,
all_libs);
}
setResultExtras(extras);
}
}

View file

@ -54,7 +54,6 @@ public abstract class ScummVM implements SurfaceHolder.Callback, Runnable {
abstract protected void getDPI(float[] values);
abstract protected void displayMessageOnOSD(String msg);
abstract protected void setWindowCaption(String caption);
abstract protected String[] getPluginDirectories();
abstract protected void showVirtualKeyboard(boolean enable);
abstract protected String[] getSysArchives();
@ -444,10 +443,6 @@ public abstract class ScummVM implements SurfaceHolder.Callback, Runnable {
}
}
File cache_dir = ScummVMApplication.getLastCacheDir();
String libname = System.mapLibraryName("scummvm");
File libpath = new File(cache_dir, libname);
System.load(libpath.getPath());
System.loadLibrary("scummvm");
}
}

View file

@ -83,13 +83,6 @@ public class ScummVMActivity extends Activity {
});
}
@Override
protected String[] getPluginDirectories() {
String[] dirs = new String[1];
dirs[0] = ScummVMApplication.getLastCacheDir().getPath();
return dirs;
}
@Override
protected void showVirtualKeyboard(final boolean enable) {
runOnUiThread(new Runnable() {

View file

@ -1,31 +0,0 @@
package org.scummvm.scummvm;
import android.app.Application;
import java.io.File;
public class ScummVMApplication extends Application {
public final static String ACTION_PLUGIN_QUERY = "org.scummvm.scummvm.action.PLUGIN_QUERY";
public final static String EXTRA_UNPACK_LIBS = "org.scummvm.scummvm.extra.UNPACK_LIBS";
public final static String EXTRA_VERSION = "org.scummvm.scummvm.extra.VERSION";
private static File _cache_dir;
@Override
public void onCreate() {
super.onCreate();
// This is still on /data :(
_cache_dir = getCacheDir();
// This is mounted noexec :(
//cache_dir = new File(Environment.getExternalStorageDirectory(),
// "/.ScummVM.tmp");
// This is owned by download manager and requires special
// permissions to access :(
//cache_dir = Environment.getDownloadCacheDirectory();
}
public static File getLastCacheDir() {
return _cache_dir;
}
}

View file

@ -1,388 +0,0 @@
package org.scummvm.scummvm;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.ProgressBar;
import java.io.IOException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
public class Unpacker extends Activity {
protected final static String LOG_TAG = "ScummVM";
// TODO don't hardcode this
private final static boolean PLUGINS_ENABLED = false;
private final static String META_NEXT_ACTIVITY =
"org.scummvm.unpacker.nextActivity";
private ProgressBar mProgress;
private File mUnpackDest; // location to unpack into
private AsyncTask<String, Integer, Void> mUnpacker;
private final static int REQUEST_MARKET = 1;
// Android 3.1+ only
public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 32;
private static class UnpackJob {
public ZipFile zipfile;
public Set<String> paths;
public UnpackJob(ZipFile zipfile, Set<String> paths) {
this.zipfile = zipfile;
this.paths = paths;
}
public long UnpackSize() {
long size = 0;
for (String path: paths) {
ZipEntry entry = zipfile.getEntry(path);
if (entry != null) size += entry.getSize();
}
return size;
}
}
private class UnpackTask extends AsyncTask<String, Integer, Void> {
@Override
protected void onProgressUpdate(Integer... progress) {
mProgress.setIndeterminate(false);
mProgress.setMax(progress[1]);
mProgress.setProgress(progress[0]);
mProgress.postInvalidate();
}
@Override
protected void onPostExecute(Void result) {
Bundle md = getMetaData();
String nextActivity = md.getString(META_NEXT_ACTIVITY);
if (nextActivity != null) {
final ComponentName cn =
ComponentName.unflattenFromString(nextActivity);
if (cn != null) {
final Intent origIntent = getIntent();
Intent intent = new Intent();
intent.setComponent(cn);
if (origIntent.getExtras() != null)
intent.putExtras(origIntent.getExtras());
intent.putExtra(Intent.EXTRA_INTENT, origIntent);
intent.setDataAndType(origIntent.getData(),
origIntent.getType());
//intent.fillIn(getIntent(), 0);
intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
Log.i(LOG_TAG,
"Starting next activity with intent " + intent);
startActivity(intent);
} else {
Log.w(LOG_TAG,
"Unable to extract a component name from " + nextActivity);
}
}
finish();
}
@Override
protected Void doInBackground(String... all_libs) {
// This will contain all unpack jobs
Map<String, UnpackJob> unpack_jobs =
new HashMap<String, UnpackJob>(all_libs.length);
// This will contain all unpack filenames (so we can
// detect stale files in the unpack directory)
Set<String> all_files = new HashSet<String>(all_libs.length);
for (String lib: all_libs) {
final Uri uri = Uri.parse(lib);
final String pkg = uri.getAuthority();
final String path = uri.getPath().substring(1); // skip first /
all_files.add(new File(path).getName());
UnpackJob job = unpack_jobs.get(pkg);
if (job == null) {
try {
// getPackageResourcePath is hidden in Context,
// but exposed in ContextWrapper...
ContextWrapper context =
new ContextWrapper(createPackageContext(pkg, 0));
ZipFile zipfile =
new ZipFile(context.getPackageResourcePath());
job = new UnpackJob(zipfile, new HashSet<String>(1));
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Package " + pkg +
" not found", e);
continue;
} catch (IOException e) {
// FIXME: show some sort of GUI error dialog
Log.e(LOG_TAG,
"Error opening ZIP for package " + pkg, e);
continue;
}
unpack_jobs.put(pkg, job);
}
job.paths.add(path);
}
// Delete stale filenames from mUnpackDest
for (File file: mUnpackDest.listFiles()) {
if (!all_files.contains(file.getName())) {
Log.i(LOG_TAG,
"Deleting stale cached file " + file);
file.delete();
}
}
int total_size = 0;
for (UnpackJob job: unpack_jobs.values())
total_size += job.UnpackSize();
publishProgress(0, total_size);
mUnpackDest.mkdirs();
int progress = 0;
for (UnpackJob job: unpack_jobs.values()) {
try {
ZipFile zipfile = job.zipfile;
for (String path: job.paths) {
ZipEntry zipentry = zipfile.getEntry(path);
if (zipentry == null)
throw new FileNotFoundException(
"Couldn't find " + path + " in zip");
File dest = new File(mUnpackDest, new File(path).getName());
if (dest.exists() &&
dest.lastModified() == zipentry.getTime() &&
dest.length() == zipentry.getSize()) {
// Already unpacked
progress += zipentry.getSize();
} else {
if (dest.exists())
Log.d(LOG_TAG,
"Replacing " + dest.getPath() +
" old.mtime=" + dest.lastModified() +
" new.mtime=" + zipentry.getTime() +
" old.size=" + dest.length() +
" new.size=" + zipentry.getSize());
else
Log.i(LOG_TAG,
"Extracting " + zipentry.getName() +
" from " + zipfile.getName() +
" to " + dest.getPath());
long next_update = progress;
InputStream in = zipfile.getInputStream(zipentry);
OutputStream out = new FileOutputStream(dest);
int len;
byte[] buffer = new byte[4096];
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
progress += len;
if (progress >= next_update) {
publishProgress(progress, total_size);
// Arbitrary limit of 2% update steps
next_update += total_size / 50;
}
}
in.close();
out.close();
dest.setLastModified(zipentry.getTime());
}
publishProgress(progress, total_size);
}
zipfile.close();
} catch (IOException e) {
// FIXME: show some sort of GUI error dialog
Log.e(LOG_TAG, "Error unpacking plugin", e);
}
}
if (progress != total_size)
Log.d(LOG_TAG, "Ended with progress " + progress +
" != total size " + total_size);
setResult(RESULT_OK);
return null;
}
}
private class PluginBroadcastReciever extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction()
.equals(ScummVMApplication.ACTION_PLUGIN_QUERY)) {
Log.e(LOG_TAG,
"Received unexpected action " + intent.getAction());
return;
}
Bundle extras = getResultExtras(false);
if (extras == null) {
// Nothing for us to do.
Unpacker.this.setResult(RESULT_OK);
finish();
}
ArrayList<String> unpack_libs =
extras.getStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS);
if (unpack_libs != null && !unpack_libs.isEmpty()) {
final String[] libs =
unpack_libs.toArray(new String[unpack_libs.size()]);
mUnpacker = new UnpackTask().execute(libs);
}
}
}
private void initPlugins() {
Bundle extras = new Bundle(1);
ArrayList<String> unpack_libs = new ArrayList<String>(1);
// This is the common ScummVM code (not really a "plugin" as such)
unpack_libs.add(new Uri.Builder()
.scheme("plugin")
.authority(getPackageName())
.path("mylib/armeabi/libscummvm.so")
.toString());
extras.putStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS,
unpack_libs);
final PackageInfo info;
try {
info = getPackageManager().getPackageInfo(getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Error finding my own info?", e);
return;
}
extras.putString(ScummVMApplication.EXTRA_VERSION, info.versionName);
Intent intent = new Intent(ScummVMApplication.ACTION_PLUGIN_QUERY);
// Android 3.1 defaults to FLAG_EXCLUDE_STOPPED_PACKAGES, and since
// none of our plugins will ever be running, that is not helpful
intent.setFlags(FLAG_INCLUDE_STOPPED_PACKAGES);
sendOrderedBroadcast(intent, Manifest.permission.SCUMMVM_PLUGIN,
new PluginBroadcastReciever(),
null, RESULT_OK, null, extras);
}
@Override
public void onCreate(Bundle b) {
super.onCreate(b);
mUnpackDest = ScummVMApplication.getLastCacheDir();
setContentView(R.layout.splash);
mProgress = (ProgressBar)findViewById(R.id.progress);
setResult(RESULT_CANCELED);
tryUnpack();
}
private void tryUnpack() {
Intent intent = new Intent(ScummVMApplication.ACTION_PLUGIN_QUERY);
List<ResolveInfo> plugins = getPackageManager()
.queryBroadcastReceivers(intent, 0);
if (PLUGINS_ENABLED && plugins.isEmpty()) {
// No plugins installed
AlertDialog.Builder alert = new AlertDialog.Builder(this)
.setTitle(R.string.no_plugins_title)
.setMessage(R.string.no_plugins_found)
.setIcon(android.R.drawable.ic_dialog_alert)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
finish();
}
})
.setNegativeButton(R.string.quit,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
final Uri uri = Uri.parse("market://search?q=ScummVM plugin");
final Intent market_intent = new Intent(Intent.ACTION_VIEW, uri);
if (getPackageManager().resolveActivity(market_intent, 0) != null) {
alert.setPositiveButton(R.string.to_market,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
try {
startActivityForResult(market_intent,
REQUEST_MARKET);
} catch (ActivityNotFoundException e) {
Log.e(LOG_TAG,
"Error starting market", e);
}
}
});
}
alert.show();
} else {
// Already have at least one plugin installed
initPlugins();
}
}
@Override
public void onStop() {
if (mUnpacker != null)
mUnpacker.cancel(true);
super.onStop();
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
switch (requestCode) {
case REQUEST_MARKET:
if (resultCode != RESULT_OK)
Log.w(LOG_TAG, "Market returned " + resultCode);
tryUnpack();
break;
}
}
private Bundle getMetaData() {
try {
ActivityInfo ai = getPackageManager()
.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
return ai.metaData;
} catch (PackageManager.NameNotFoundException e) {
Log.w(LOG_TAG, "Unable to find my own meta-data", e);
return new Bundle();
}
}
}

Some files were not shown because too many files have changed in this diff Show more