[Scummvm-git-logs] scummvm master -> b658831920a54c054b77743b895060225b53203b

bluegr noreply at scummvm.org
Sun Mar 8 17:02:46 UTC 2026


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
b658831920 AGOS: Elvira 1/2 Atari ST - Music support


Commit: b658831920a54c054b77743b895060225b53203b
    https://github.com/scummvm/scummvm/commit/b658831920a54c054b77743b895060225b53203b
Author: Robert Megone (robert.megone at gmail.com)
Date: 2026-03-08T19:02:41+02:00

Commit Message:
AGOS: Elvira 1/2 Atari ST - Music support

Changed paths:
  A audio/softsynth/ym2149.cpp
  A audio/softsynth/ym2149.h
  A audio/ym2149.cpp
  A audio/ym2149.h
  A engines/agos/drivers/elvira_atarist.cpp
  A engines/agos/drivers/elvira_atarist.h
    audio/module.mk
    engines/agos/agos.cpp
    engines/agos/agos.h
    engines/agos/module.mk
    engines/agos/res_snd.cpp


diff --git a/audio/module.mk b/audio/module.mk
index 14d58f959fa..bff9dc36696 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -28,6 +28,7 @@ MODULE_OBJS := \
 	null.o \
 	rate.o \
 	sid.o \
+	ym2149.o \
 	timestamp.o \
 	decoders/3do.o \
 	decoders/aac.o \
@@ -69,7 +70,8 @@ MODULE_OBJS := \
 	softsynth/fluidsynth.o \
 	softsynth/eas.o \
 	softsynth/pcspk.o \
-	softsynth/ay8912.o
+	softsynth/ay8912.o \
+	softsynth/ym2149.o
 
 ifndef DISABLE_NUKED_OPL
 MODULE_OBJS += \
diff --git a/audio/softsynth/ym2149.cpp b/audio/softsynth/ym2149.cpp
new file mode 100644
index 00000000000..fd3059c5489
--- /dev/null
+++ b/audio/softsynth/ym2149.cpp
@@ -0,0 +1,809 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* Adapted from Hatari - https://framagit.org/hatari/hatari/
+ * 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 Soft-
+ * ware 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, see <https://www.gnu.org/licenses/>.
+ *
+ * Linking Hatari statically or dynamically with other modules is making a
+ * combined work based on Hatari. Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+
+ *
+ * In addition, as a special exception, the copyright holders of Hatari give you
+ * permission to combine Hatari with free software programs or libraries that are
+ * released under the GNU LGPL and with code included in the standard release
+ * of the IPF support library (a.k.a. libcapsimage) under the Software Preservation
+ * Society Licence Agreement.
+ */
+
+#include "audio/softsynth/ym2149.h"
+
+#include "common/util.h"
+#include "common/scummsys.h"
+
+namespace Audio {
+
+YM2149Emu::YM2149Emu()
+	: _toneAPer(1), _toneACount(0), _toneAVal(YM_SQUARE_UP),
+	  _toneBPer(1), _toneBCount(0), _toneBVal(YM_SQUARE_UP),
+	  _toneCPer(1), _toneCCount(0), _toneCVal(YM_SQUARE_UP),
+	  _noisePer(1), _noiseCount(0), _noiseVal(0),
+	  _envPer(1), _envCount(0),
+	  _envPos(0), _envShape(0),
+	  _mixerTA(0), _mixerTB(0), _mixerTC(0),
+	  _mixerNA(0), _mixerNB(0), _mixerNC(0),
+	  _rndRack(1), _freqDiv2(0),
+	  _envMask3Voices(0), _vol3Voices(0),
+	  _YMBuffer250PosWrite(0), _YMBuffer250PosRead(0),
+	  _posFractWeightedN(0), _rate(44100) {
+	memset(_soundRegs, 0, sizeof(_soundRegs));
+	memset(_YMBuffer250, 0, sizeof(_YMBuffer250));
+}
+
+YM2149Emu::~YM2149Emu() {
+}
+
+bool YM2149Emu::init() {
+	initOnce();
+	setOutputRate(getRate());
+	reset();
+	return true;
+}
+
+const uint32 YM2149Emu::YmVolume4to5[32] = {
+	0, 1, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31,
+	0, 1, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31
+};
+
+
+const int YM2149Emu::YmEnvDef[16][3] = {
+	{ ENV_GODOWN, ENV_DOWN, ENV_DOWN },
+	{ ENV_GODOWN, ENV_DOWN, ENV_DOWN },
+	{ ENV_GODOWN, ENV_DOWN, ENV_DOWN },
+	{ ENV_GODOWN, ENV_DOWN, ENV_DOWN },
+	{ ENV_GOUP, ENV_DOWN, ENV_DOWN },
+	{ ENV_GOUP, ENV_DOWN, ENV_DOWN },
+	{ ENV_GOUP, ENV_DOWN, ENV_DOWN },
+	{ ENV_GOUP, ENV_DOWN, ENV_DOWN },
+	{ ENV_GODOWN, ENV_GODOWN, ENV_GODOWN },
+	{ ENV_GODOWN, ENV_DOWN, ENV_DOWN },
+	{ ENV_GODOWN, ENV_GOUP, ENV_GODOWN },
+	{ ENV_GODOWN, ENV_UP, ENV_UP },
+	{ ENV_GOUP, ENV_GOUP, ENV_GOUP },
+	{ ENV_GOUP, ENV_UP, ENV_UP },
+	{ ENV_GOUP, ENV_GODOWN, ENV_GOUP },
+	{ ENV_GOUP, ENV_DOWN, ENV_DOWN }
+};
+
+
+ /* Sixteen level by Three voice YM2149 volume_table[C][B][A]
+ * Data measured by Paulo Simoes. Copyright 2012 Paulo Simoes.
+ */
+
+uint16 YM2149Emu::YmEnvWaves[16][32 * 3];
+
+const uint16 YM2149Emu::volumeTable[16][16][16] =
+{
+	{
+		{     0,   120,   251,   385,   598,   840,  1258,  1725,  2536,  3483,  5151,  7054, 10367, 14634, 22931, 34469 },
+		{   120,   283,   419,   556,   771,  1009,  1433,  1894,  2702,  3645,  5310,  7207, 10508, 14757, 23015, 34480 },
+		{   251,   419,   553,   692,   903,  1147,  1565,  2026,  2833,  3775,  5439,  7325, 10619, 14857, 23082, 34493 },
+		{   385,   556,   692,   828,  1038,  1286,  1704,  2164,  2967,  3906,  5564,  7450, 10733, 14958, 23156, 34509 },
+		{   598,   771,   903,  1038,  1261,  1502,  1925,  2382,  3176,  4113,  5764,  7649, 10915, 15123, 23272, 34538 },
+		{   840,  1009,  1147,  1286,  1502,  1747,  2167,  2626,  3417,  4348,  5985,  7851, 11109, 15302, 23400, 34570 },
+		{  1258,  1433,  1565,  1704,  1925,  2167,  2572,  3019,  3812,  4743,  6367,  8205, 11457, 15616, 23639, 34646 },
+		{  1725,  1894,  2026,  2164,  2382,  2626,  3019,  3487,  4266,  5173,  6792,  8517, 11852, 15979, 23925, 34754 },
+		{  2536,  2702,  2833,  2967,  3176,  3417,  3812,  4266,  5048,  5947,  7542,  9227, 12543, 16613, 24440, 34992 },
+		{  3483,  3645,  3775,  3906,  4113,  4348,  4743,  5173,  5947,  6847,  8353, 10089, 13357, 17288, 25084, 35355 },
+		{  5151,  5310,  5439,  5564,  5764,  5985,  6367,  6792,  7542,  8353,  9780, 11549, 14769, 18591, 26237, 36119 },
+		{  7054,  7207,  7325,  7450,  7649,  7851,  8205,  8517,  9227, 10089, 11549, 13292, 16452, 20206, 27628, 37143 },
+		{ 10367, 10508, 10619, 10733, 10915, 11109, 11457, 11852, 12543, 13357, 14769, 16452, 19431, 23228, 30481, 39499 },
+		{ 14634, 14757, 14857, 14958, 15123, 15302, 15616, 15979, 16613, 17288, 18591, 20206, 23228, 26871, 34097, 42635 },
+		{ 22931, 23015, 23082, 23156, 23272, 23400, 23639, 23925, 24440, 25084, 26237, 27628, 30481, 34097, 40524, 48336 },
+		{ 34469, 34480, 34493, 34509, 34538, 34570, 34646, 34754, 34992, 35355, 36119, 37143, 39499, 42635, 48336, 55377 }
+	},
+	{
+		{   120,   283,   419,   556,   771,  1009,  1433,  1894,  2702,  3645,  5310,  7207, 10508, 14757, 23015, 34480 },
+		{   283,   453,   589,   723,   941,  1179,  1601,  2059,  2864,  3811,  5472,  7358, 10646, 14884, 23101, 34500 },
+		{   419,   589,   727,   863,  1077,  1316,  1737,  2194,  2998,  3938,  5599,  7478, 10755, 14980, 23168, 34509 },
+		{   556,   723,   863,   997,  1212,  1453,  1874,  2331,  3130,  4067,  5726,  7602, 10872, 15084, 23243, 34526 },
+		{   771,   941,  1077,  1212,  1427,  1674,  2089,  2551,  3345,  4273,  5916,  7792, 11056, 15245, 23354, 34556 },
+		{  1009,  1179,  1316,  1453,  1674,  1915,  2333,  2788,  3579,  4509,  6142,  7993, 11249, 15426, 23486, 34585 },
+		{  1433,  1601,  1737,  1874,  2089,  2333,  2735,  3188,  3976,  4898,  6516,  8315, 11594, 15735, 23718, 34662 },
+		{  1894,  2059,  2194,  2331,  2551,  2788,  3188,  3649,  4423,  5330,  6948,  8656, 11984, 16094, 24004, 34771 },
+		{  2702,  2864,  2998,  3130,  3345,  3579,  3976,  4423,  5202,  6104,  7684,  9368, 12668, 16724, 24512, 35007 },
+		{  3645,  3811,  3938,  4067,  4273,  4509,  4898,  5330,  6104,  6999,  8449, 10221, 13483, 17379, 25149, 35366 },
+		{  5310,  5472,  5599,  5726,  5916,  6142,  6516,  6948,  7684,  8449,  9917, 11680, 14885, 18689, 26297, 36126 },
+		{  7207,  7358,  7478,  7602,  7792,  7993,  8315,  8656,  9368, 10221, 11680, 13417, 16558, 20295, 27677, 37144 },
+		{ 10508, 10646, 10755, 10872, 11056, 11249, 11594, 11984, 12668, 13483, 14885, 16558, 19523, 23300, 30508, 39500 },
+		{ 14757, 14884, 14980, 15084, 15245, 15426, 15735, 16094, 16724, 17379, 18689, 20295, 23300, 26918, 34108, 42636 },
+		{ 23015, 23101, 23168, 23243, 23354, 23486, 23718, 24004, 24512, 25149, 26297, 27677, 30508, 34108, 40527, 48337 },
+		{ 34480, 34500, 34509, 34526, 34556, 34585, 34662, 34771, 35007, 35366, 36126, 37144, 39500, 42636, 48337, 55377 }
+	},
+	{
+		{   251,   419,   553,   692,   903,  1147,  1565,  2026,  2833,  3775,  5439,  7325, 10619, 14857, 23082, 34493 },
+		{   419,   589,   727,   863,  1077,  1316,  1737,  2194,  2998,  3938,  5599,  7478, 10755, 14980, 23168, 34509 },
+		{   553,   727,   863,   999,  1213,  1454,  1873,  2329,  3129,  4071,  5727,  7600, 10871, 15085, 23243, 34524 },
+		{   692,   863,   999,  1135,  1351,  1592,  2011,  2466,  3264,  4199,  5849,  7724, 10990, 15187, 23315, 34541 },
+		{   903,  1077,  1213,  1351,  1567,  1811,  2230,  2689,  3476,  4402,  6041,  7910, 11165, 15342, 23425, 34568 },
+		{  1147,  1316,  1454,  1592,  1811,  2054,  2469,  2924,  3716,  4640,  6265,  8109, 11365, 15526, 23554, 34601 },
+		{  1565,  1737,  1873,  2011,  2230,  2469,  2873,  3323,  4105,  5023,  6639,  8395, 11700, 15831, 23786, 34675 },
+		{  2026,  2194,  2329,  2466,  2689,  2924,  3323,  3777,  4553,  5456,  7069,  8770, 12097, 16188, 24070, 34781 },
+		{  2833,  2998,  3129,  3264,  3476,  3716,  4105,  4553,  5330,  6231,  7804,  9480, 12776, 16820, 24574, 35020 },
+		{  3775,  3938,  4071,  4199,  4402,  4640,  5023,  5456,  6231,  7115,  8554, 10329, 13586, 17460, 25207, 35381 },
+		{  5439,  5599,  5727,  5849,  6041,  6265,  6639,  7069,  7804,  8554, 10029, 11784, 14980, 18770, 26344, 36134 },
+		{  7325,  7478,  7600,  7724,  7910,  8109,  8395,  8770,  9480, 10329, 11784, 13519, 16643, 20370, 27719, 37151 },
+		{ 10619, 10755, 10871, 10990, 11165, 11365, 11700, 12097, 12776, 13586, 14980, 16643, 19601, 23359, 30533, 39501 },
+		{ 14857, 14980, 15085, 15187, 15342, 15526, 15831, 16188, 16820, 17460, 18770, 20370, 23359, 26957, 34125, 42637 },
+		{ 23082, 23168, 23243, 23315, 23425, 23554, 23786, 24070, 24574, 25207, 26344, 27719, 30533, 34125, 40529, 48338 },
+		{ 34493, 34509, 34524, 34541, 34568, 34601, 34675, 34781, 35020, 35381, 36134, 37151, 39501, 42637, 48338, 55378 }
+	},
+	{
+		{   385,   556,   692,   828,  1038,  1286,  1704,  2164,  2967,  3906,  5564,  7450, 10733, 14958, 23156, 34509 },
+		{   556,   723,   863,   997,  1212,  1453,  1874,  2331,  3130,  4067,  5726,  7602, 10872, 15084, 23243, 34526 },
+		{   692,   863,   999,  1135,  1351,  1592,  2011,  2466,  3264,  4199,  5849,  7724, 10990, 15187, 23315, 34541 },
+		{   828,   997,  1135,  1276,  1482,  1731,  2146,  2605,  3397,  4324,  5970,  7841, 11105, 15285, 23389, 34561 },
+		{  1038,  1212,  1351,  1482,  1703,  1945,  2363,  2819,  3606,  4535,  6167,  8026, 11271, 15445, 23493, 34587 },
+		{  1286,  1453,  1592,  1731,  1945,  2185,  2605,  3053,  3844,  4769,  6395,  8222, 11476, 15625, 23625, 34620 },
+		{  1704,  1874,  2011,  2146,  2363,  2605,  3004,  3452,  4232,  5154,  6762,  8488, 11803, 15932, 23851, 34692 },
+		{  2164,  2331,  2466,  2605,  2819,  3053,  3452,  3909,  4682,  5580,  7190,  8888, 12204, 16280, 24134, 34798 },
+		{  2967,  3130,  3264,  3397,  3606,  3844,  4232,  4682,  5456,  6350,  7915,  9593, 12882, 16907, 24637, 35034 },
+		{  3906,  4067,  4199,  4324,  4535,  4769,  5154,  5580,  6350,  7235,  8667, 10439, 13680, 17548, 25267, 35392 },
+		{  5564,  5726,  5849,  5970,  6167,  6395,  6762,  7190,  7915,  8667, 10135, 11887, 15073, 18851, 26391, 36142 },
+		{  7450,  7602,  7724,  7841,  8026,  8222,  8488,  8888,  9593, 10439, 11887, 13620, 16729, 20446, 27762, 37157 },
+		{ 10733, 10872, 10990, 11105, 11271, 11476, 11803, 12204, 12882, 13680, 15073, 16729, 19676, 23419, 30559, 39503 },
+		{ 14958, 15084, 15187, 15285, 15445, 15625, 15932, 16280, 16907, 17548, 18851, 20446, 23419, 26998, 34139, 42639 },
+		{ 23156, 23243, 23315, 23389, 23493, 23625, 23851, 24134, 24637, 25267, 26391, 27762, 30559, 34139, 40534, 48339 },
+		{ 34509, 34526, 34541, 34561, 34587, 34620, 34692, 34798, 35034, 35392, 36142, 37157, 39503, 42639, 48339, 55378 }
+	},
+	{
+		{   598,   771,   903,  1038,  1261,  1502,  1925,  2382,  3176,  4113,  5764,  7649, 10915, 15123, 23272, 34538 },
+		{   771,   941,  1077,  1212,  1427,  1674,  2089,  2551,  3345,  4273,  5916,  7792, 11056, 15245, 23354, 34556 },
+		{   903,  1077,  1213,  1351,  1567,  1811,  2230,  2689,  3476,  4402,  6041,  7910, 11165, 15342, 23425, 34568 },
+		{  1038,  1212,  1351,  1482,  1703,  1945,  2363,  2819,  3606,  4535,  6167,  8026, 11271, 15445, 23493, 34587 },
+		{  1261,  1427,  1567,  1703,  1916,  2162,  2575,  3032,  3817,  4738,  6364,  8201, 11449, 15605, 23608, 34613 },
+		{  1502,  1674,  1811,  1945,  2162,  2399,  2817,  3263,  4051,  4974,  6588,  8362, 11657, 15784, 23741, 34649 },
+		{  1925,  2089,  2230,  2363,  2575,  2817,  3212,  3664,  4440,  5348,  6954,  8660, 11979, 16087, 23965, 34720 },
+		{  2382,  2551,  2689,  2819,  3032,  3263,  3664,  4111,  4880,  5781,  7385,  9072, 12371, 16431, 24240, 34823 },
+		{  3176,  3345,  3476,  3606,  3817,  4051,  4440,  4880,  5653,  6548,  8104,  9776, 13045, 17045, 24740, 35060 },
+		{  4113,  4273,  4402,  4535,  4738,  4974,  5348,  5781,  6548,  7426,  8845, 10614, 13842, 17683, 25362, 35414 },
+		{  5764,  5916,  6041,  6167,  6364,  6588,  6954,  7385,  8104,  8845, 10314, 12061, 15219, 18983, 26463, 36154 },
+		{  7649,  7792,  7910,  8026,  8201,  8362,  8660,  9072,  9776, 10614, 12061, 13783, 16862, 20567, 27833, 37166 },
+		{ 10915, 11056, 11165, 11271, 11449, 11657, 11979, 12371, 13045, 13842, 15219, 16862, 19799, 23517, 30606, 39513 },
+		{ 15123, 15245, 15342, 15445, 15605, 15784, 16087, 16431, 17045, 17683, 18983, 20567, 23517, 27066, 34164, 42641 },
+		{ 23272, 23354, 23425, 23493, 23608, 23741, 23965, 24240, 24740, 25362, 26463, 27833, 30606, 34164, 40537, 48340 },
+		{ 34538, 34556, 34568, 34587, 34613, 34649, 34720, 34823, 35060, 35414, 36154, 37166, 39513, 42641, 48340, 55379 }
+	},
+	{
+		{   840,  1009,  1147,  1286,  1502,  1747,  2167,  2626,  3417,  4348,  5985,  7851, 11109, 15302, 23400, 34570 },
+		{  1009,  1179,  1316,  1453,  1674,  1915,  2333,  2788,  3579,  4509,  6142,  7993, 11249, 15426, 23486, 34585 },
+		{  1147,  1316,  1454,  1592,  1811,  2054,  2469,  2924,  3716,  4640,  6265,  8109, 11365, 15526, 23554, 34601 },
+		{  1286,  1453,  1592,  1731,  1945,  2185,  2605,  3053,  3844,  4769,  6395,  8222, 11476, 15625, 23625, 34620 },
+		{  1502,  1674,  1811,  1945,  2162,  2399,  2817,  3263,  4051,  4974,  6588,  8362, 11657, 15784, 23741, 34649 },
+		{  1747,  1915,  2054,  2185,  2399,  2640,  3054,  3501,  4282,  5198,  6806,  8530, 11855, 15961, 23871, 34688 },
+		{  2167,  2333,  2469,  2605,  2817,  3054,  3449,  3898,  4667,  5571,  7172,  8864, 12172, 16261, 24094, 34758 },
+		{  2626,  2788,  2924,  3053,  3263,  3501,  3898,  4342,  5110,  6002,  7594,  9274, 12564, 16600, 24361, 34858 },
+		{  3417,  3579,  3716,  3844,  4051,  4282,  4667,  5110,  5878,  6764,  8289,  9978, 13233, 17183, 24857, 35090 },
+		{  4348,  4509,  4640,  4769,  4974,  5198,  5571,  6002,  6764,  7634,  9046, 10809, 14023, 17843, 25474, 35441 },
+		{  5985,  6142,  6265,  6395,  6588,  6806,  7172,  7594,  8289,  9046, 10510, 12249, 15389, 19129, 26571, 36174 },
+		{  7851,  7993,  8109,  8222,  8362,  8530,  8864,  9274,  9978, 10809, 12249, 13962, 17018, 20703, 27915, 37183 },
+		{ 11109, 11249, 11365, 11476, 11657, 11855, 12172, 12564, 13233, 14023, 15389, 17018, 19935, 23632, 30665, 39517 },
+		{ 15302, 15426, 15526, 15625, 15784, 15961, 16261, 16600, 17183, 17843, 19129, 20703, 23632, 27149, 34193, 42643 },
+		{ 23400, 23486, 23554, 23625, 23741, 23871, 24094, 24361, 24857, 25474, 26571, 27915, 30665, 34193, 40540, 48341 },
+		{ 34570, 34585, 34601, 34620, 34649, 34688, 34758, 34858, 35090, 35441, 36174, 37183, 39517, 42643, 48341, 55379 }
+	},
+	{
+		{  1258,  1433,  1565,  1704,  1925,  2167,  2572,  3019,  3812,  4743,  6367,  8205, 11457, 15616, 23639, 34646 },
+		{  1433,  1601,  1737,  1874,  2089,  2333,  2735,  3188,  3976,  4898,  6516,  8315, 11594, 15735, 23718, 34662 },
+		{  1565,  1737,  1873,  2011,  2230,  2469,  2873,  3323,  4105,  5023,  6639,  8395, 11700, 15831, 23786, 34675 },
+		{  1704,  1874,  2011,  2146,  2363,  2605,  3004,  3452,  4232,  5154,  6762,  8488, 11803, 15932, 23851, 34692 },
+		{  1925,  2089,  2230,  2363,  2575,  2817,  3212,  3664,  4440,  5348,  6954,  8660, 11979, 16087, 23965, 34720 },
+		{  2167,  2333,  2469,  2605,  2817,  3054,  3449,  3898,  4667,  5571,  7172,  8864, 12172, 16261, 24094, 34758 },
+		{  2572,  2735,  2873,  3004,  3212,  3449,  3854,  4302,  5067,  5961,  7550,  9224, 12519, 16573, 24328, 34836 },
+		{  3019,  3188,  3323,  3452,  3664,  3898,  4302,  4742,  5503,  6387,  7962,  9634, 12895, 16899, 24587, 34932 },
+		{  3812,  3976,  4105,  4232,  4440,  4667,  5067,  5503,  6267,  7143,  8571, 10335, 13563, 17414, 25071, 35162 },
+		{  4743,  4898,  5023,  5154,  5348,  5571,  5961,  6387,  7143,  8004,  9406, 11158, 14338, 18124, 25673, 35504 },
+		{  6367,  6516,  6639,  6762,  6954,  7172,  7550,  7962,  8571,  9406, 10856, 12580, 15691, 19396, 26692, 36217 },
+		{  8205,  8315,  8395,  8488,  8660,  8864,  9224,  9634, 10335, 11158, 12580, 14275, 17244, 20952, 28071, 37216 },
+		{ 11457, 11594, 11700, 11803, 11979, 12172, 12519, 12895, 13563, 14338, 15691, 17244, 20186, 23839, 30781, 39535 },
+		{ 15616, 15735, 15831, 15932, 16087, 16261, 16573, 16899, 17414, 18124, 19396, 20952, 23839, 27307, 34258, 42647 },
+		{ 23639, 23718, 23786, 23851, 23965, 24094, 24328, 24587, 25071, 25673, 26692, 28071, 30781, 34258, 40553, 48342 },
+		{ 34646, 34662, 34675, 34692, 34720, 34758, 34836, 34932, 35162, 35504, 36217, 37216, 39535, 42647, 48342, 55380 }
+	},
+	{
+		{  1725,  1894,  2026,  2164,  2382,  2626,  3019,  3487,  4266,  5173,  6792,  8517, 11852, 15979, 23925, 34754 },
+		{  1894,  2059,  2194,  2331,  2551,  2788,  3188,  3649,  4423,  5330,  6948,  8656, 11984, 16094, 24004, 34771 },
+		{  2026,  2194,  2329,  2466,  2689,  2924,  3323,  3777,  4553,  5456,  7069,  8770, 12097, 16188, 24070, 34781 },
+		{  2164,  2331,  2466,  2605,  2819,  3053,  3452,  3909,  4682,  5580,  7190,  8888, 12204, 16280, 24134, 34798 },
+		{  2382,  2551,  2689,  2819,  3032,  3263,  3664,  4111,  4880,  5781,  7385,  9072, 12371, 16431, 24240, 34823 },
+		{  2626,  2788,  2924,  3053,  3263,  3501,  3898,  4342,  5110,  6002,  7594,  9274, 12564, 16600, 24361, 34858 },
+		{  3019,  3188,  3323,  3452,  3664,  3898,  4302,  4742,  5503,  6387,  7962,  9634, 12895, 16899, 24587, 34932 },
+		{  3487,  3649,  3777,  3909,  4111,  4342,  4742,  5177,  5934,  6818,  8332, 10028, 13268, 17198, 24845, 35038 },
+		{  4266,  4423,  4553,  4682,  4880,  5110,  5503,  5934,  6687,  7559,  8964, 10727, 13927, 17732, 25320, 35258 },
+		{  5173,  5330,  5456,  5580,  5781,  6002,  6387,  6818,  7559,  8353,  9797, 11537, 14695, 18443, 25905, 35591 },
+		{  6792,  6948,  7069,  7190,  7385,  7594,  7962,  8332,  8964,  9797, 11242, 12949, 16028, 19695, 26891, 36270 },
+		{  8517,  8656,  8770,  8888,  9072,  9274,  9634, 10028, 10727, 11537, 12949, 14625, 17520, 21231, 28260, 37278 },
+		{ 11852, 11984, 12097, 12204, 12371, 12564, 12895, 13268, 13927, 14695, 16028, 17520, 20469, 24080, 30926, 39567 },
+		{ 15979, 16094, 16188, 16280, 16431, 16600, 16899, 17198, 17732, 18443, 19695, 21231, 24080, 27498, 34345, 42662 },
+		{ 23925, 24004, 24070, 24134, 24240, 24361, 24587, 24845, 25320, 25905, 26891, 28260, 30926, 34345, 40575, 48344 },
+		{ 34754, 34771, 34781, 34798, 34823, 34858, 34932, 35038, 35258, 35591, 36270, 37278, 39567, 42662, 48344, 55381 }
+	},
+	{
+		{  2536,  2702,  2833,  2967,  3176,  3417,  3812,  4266,  5048,  5947,  7542,  9227, 12543, 16613, 24440, 34992 },
+		{  2702,  2864,  2998,  3130,  3345,  3579,  3976,  4423,  5202,  6104,  7684,  9368, 12668, 16724, 24512, 35007 },
+		{  2833,  2998,  3129,  3264,  3476,  3716,  4105,  4553,  5330,  6231,  7804,  9480, 12776, 16820, 24574, 35020 },
+		{  2967,  3130,  3264,  3397,  3606,  3844,  4232,  4682,  5456,  6350,  7915,  9593, 12882, 16907, 24637, 35034 },
+		{  3176,  3345,  3476,  3606,  3817,  4051,  4440,  4880,  5653,  6548,  8104,  9776, 13045, 17045, 24740, 35060 },
+		{  3417,  3579,  3716,  3844,  4051,  4282,  4667,  5110,  5878,  6764,  8289,  9978, 13233, 17183, 24857, 35090 },
+		{  3812,  3976,  4105,  4232,  4440,  4667,  5067,  5503,  6267,  7143,  8571, 10335, 13563, 17414, 25071, 35162 },
+		{  4266,  4423,  4553,  4682,  4880,  5110,  5503,  5934,  6687,  7559,  8964, 10727, 13927, 17732, 25320, 35258 },
+		{  5048,  5202,  5330,  5456,  5653,  5878,  6267,  6687,  7432,  8260,  9665, 11412, 14575, 18313, 25781, 35482 },
+		{  5947,  6104,  6231,  6350,  6548,  6764,  7143,  7559,  8260,  8997, 10490, 12209, 15324, 19011, 26331, 35795 },
+		{  7542,  7684,  7804,  7915,  8104,  8289,  8571,  8964,  9665, 10490, 11913, 13590, 16629, 20233, 27289, 36423 },
+		{  9227,  9368,  9480,  9593,  9776,  9978, 10335, 10727, 11412, 12209, 13590, 15237, 18080, 21736, 28624, 37418 },
+		{ 12543, 12668, 12776, 12882, 13045, 13233, 13563, 13927, 14575, 15324, 16629, 18080, 20977, 24523, 31218, 39649 },
+		{ 16613, 16724, 16820, 16907, 17045, 17183, 17414, 17732, 18313, 19011, 20233, 21736, 24523, 27863, 34544, 42695 },
+		{ 24440, 24512, 24574, 24637, 24740, 24857, 25071, 25320, 25781, 26331, 27289, 28624, 31218, 34544, 40634, 48348 },
+		{ 34992, 35007, 35020, 35034, 35060, 35090, 35162, 35258, 35482, 35795, 36423, 37418, 39649, 42695, 48348, 55382 }
+	},
+	{
+		{  3483,  3645,  3775,  3906,  4113,  4348,  4743,  5173,  5947,  6847,  8353, 10089, 13357, 17288, 25084, 35355 },
+		{  3645,  3811,  3938,  4067,  4273,  4509,  4898,  5330,  6104,  6999,  8449, 10221, 13483, 17379, 25149, 35366 },
+		{  3775,  3938,  4071,  4199,  4402,  4640,  5023,  5456,  6231,  7115,  8554, 10329, 13586, 17460, 25207, 35381 },
+		{  3906,  4067,  4199,  4324,  4535,  4769,  5154,  5580,  6350,  7235,  8667, 10439, 13680, 17548, 25267, 35392 },
+		{  4113,  4273,  4402,  4535,  4738,  4974,  5348,  5781,  6548,  7426,  8845, 10614, 13842, 17683, 25362, 35414 },
+		{  4348,  4509,  4640,  4769,  4974,  5198,  5571,  6002,  6764,  7634,  9046, 10809, 14023, 17843, 25474, 35441 },
+		{  4743,  4898,  5023,  5154,  5348,  5571,  5961,  6387,  7143,  8004,  9406, 11158, 14338, 18124, 25673, 35504 },
+		{  5173,  5330,  5456,  5580,  5781,  6002,  6387,  6818,  7559,  8353,  9797, 11537, 14695, 18443, 25905, 35591 },
+		{  5947,  6104,  6231,  6350,  6548,  6764,  7143,  7559,  8260,  8997, 10490, 12209, 15324, 19011, 26331, 35795 },
+		{  6847,  6999,  7115,  7235,  7426,  7634,  8004,  8353,  8997,  9829, 11306, 12998, 16067, 19696, 26808, 36099 },
+		{  8353,  8449,  8554,  8667,  8845,  9046,  9406,  9797, 10490, 11306, 12706, 14348, 17258, 20884, 27805, 36672 },
+		{ 10089, 10221, 10329, 10439, 10614, 10809, 11158, 11537, 12209, 12998, 14348, 15967, 18756, 22352, 29098, 37647 },
+		{ 13357, 13483, 13586, 13680, 13842, 14023, 14338, 14695, 15324, 16067, 17258, 18756, 21599, 25072, 31613, 39810 },
+		{ 17288, 17379, 17460, 17548, 17683, 17843, 18124, 18443, 19011, 19696, 20884, 22352, 25072, 28332, 34841, 42772 },
+		{ 25084, 25149, 25207, 25267, 25362, 25474, 25673, 25905, 26331, 26808, 27805, 29098, 31613, 34841, 40758, 48353 },
+		{ 35355, 35366, 35381, 35392, 35414, 35441, 35504, 35591, 35795, 36099, 36672, 37647, 39810, 42772, 48353, 55383 }
+	},
+	{
+		{  5151,  5310,  5439,  5564,  5764,  5985,  6367,  6792,  7542,  8353,  9780, 11549, 14769, 18591, 26237, 36119 },
+		{  5310,  5472,  5599,  5726,  5916,  6142,  6516,  6948,  7684,  8449,  9917, 11680, 14885, 18689, 26297, 36126 },
+		{  5439,  5599,  5727,  5849,  6041,  6265,  6639,  7069,  7804,  8554, 10029, 11784, 14980, 18770, 26344, 36134 },
+		{  5564,  5726,  5849,  5970,  6167,  6395,  6762,  7190,  7915,  8667, 10135, 11887, 15073, 18851, 26391, 36142 },
+		{  5764,  5916,  6041,  6167,  6364,  6588,  6954,  7385,  8104,  8845, 10314, 12061, 15219, 18983, 26463, 36154 },
+		{  5985,  6142,  6265,  6395,  6588,  6806,  7172,  7594,  8289,  9046, 10510, 12249, 15389, 19129, 26571, 36174 },
+		{  6367,  6516,  6639,  6762,  6954,  7172,  7550,  7962,  8571,  9406, 10856, 12580, 15691, 19396, 26692, 36217 },
+		{  6792,  6948,  7069,  7190,  7385,  7594,  7962,  8332,  8964,  9797, 11242, 12949, 16028, 19695, 26891, 36270 },
+		{  7542,  7684,  7804,  7915,  8104,  8289,  8571,  8964,  9665, 10490, 11913, 13590, 16629, 20233, 27289, 36423 },
+		{  8353,  8449,  8554,  8667,  8845,  9046,  9406,  9797, 10490, 11306, 12706, 14348, 17258, 20884, 27805, 36672 },
+		{  9780,  9917, 10029, 10135, 10314, 10510, 10856, 11242, 11913, 12706, 14111, 15703, 18497, 22076, 28804, 37321 },
+		{ 11549, 11680, 11784, 11887, 12061, 12249, 12580, 12949, 13590, 14348, 15703, 17212, 19984, 23485, 30030, 38230 },
+		{ 14769, 14885, 14980, 15073, 15219, 15389, 15691, 16028, 16629, 17258, 18497, 19984, 22737, 26094, 32422, 40256 },
+		{ 18591, 18689, 18770, 18851, 18983, 19129, 19396, 19695, 20233, 20884, 22076, 23485, 26094, 29241, 35499, 43067 },
+		{ 26237, 26297, 26344, 26391, 26463, 26571, 26692, 26891, 27289, 27805, 28804, 30030, 32422, 35499, 41131, 48449 },
+		{ 36119, 36126, 36134, 36142, 36154, 36174, 36217, 36270, 36423, 36672, 37321, 38230, 40256, 43067, 48449, 55385 }
+	},
+	{
+		{  7054,  7207,  7325,  7450,  7649,  7851,  8205,  8517,  9227, 10089, 11549, 13292, 16452, 20206, 27628, 37143 },
+		{  7207,  7358,  7478,  7602,  7792,  7993,  8315,  8656,  9368, 10221, 11680, 13417, 16558, 20295, 27677, 37144 },
+		{  7325,  7478,  7600,  7724,  7910,  8109,  8395,  8770,  9480, 10329, 11784, 13519, 16643, 20370, 27719, 37151 },
+		{  7450,  7602,  7724,  7841,  8026,  8222,  8488,  8888,  9593, 10439, 11887, 13620, 16729, 20446, 27762, 37157 },
+		{  7649,  7792,  7910,  8026,  8201,  8362,  8660,  9072,  9776, 10614, 12061, 13783, 16862, 20567, 27833, 37166 },
+		{  7851,  7993,  8109,  8222,  8362,  8530,  8864,  9274,  9978, 10809, 12249, 13962, 17018, 20703, 27915, 37183 },
+		{  8205,  8315,  8395,  8488,  8660,  8864,  9224,  9634, 10335, 11158, 12580, 14275, 17244, 20952, 28071, 37216 },
+		{  8517,  8656,  8770,  8888,  9072,  9274,  9634, 10028, 10727, 11537, 12949, 14625, 17520, 21231, 28260, 37278 },
+		{  9227,  9368,  9480,  9593,  9776,  9978, 10335, 10727, 11412, 12209, 13590, 15237, 18080, 21736, 28624, 37418 },
+		{ 10089, 10221, 10329, 10439, 10614, 10809, 11158, 11537, 12209, 12998, 14348, 15967, 18756, 22352, 29098, 37647 },
+		{ 11549, 11680, 11784, 11887, 12061, 12249, 12580, 12949, 13590, 14348, 15703, 17212, 19984, 23485, 30030, 38230 },
+		{ 13292, 13417, 13519, 13620, 13783, 13962, 14275, 14625, 15237, 15967, 17212, 18672, 21436, 24840, 31211, 39115 },
+		{ 16452, 16558, 16643, 16729, 16862, 17018, 17244, 17520, 18080, 18756, 19984, 21436, 24097, 27224, 33479, 40993 },
+		{ 20206, 20295, 20370, 20446, 20567, 20703, 20952, 21231, 21736, 22352, 23485, 24840, 27224, 30386, 36330, 43638 },
+		{ 27628, 27677, 27719, 27762, 27833, 27915, 28071, 28260, 28624, 29098, 30030, 31211, 33479, 36330, 41775, 48724 },
+		{ 37143, 37144, 37151, 37157, 37166, 37183, 37216, 37278, 37418, 37647, 38230, 39115, 40993, 43638, 48724, 55407 }
+	},
+	{
+		{ 10367, 10508, 10619, 10733, 10915, 11109, 11457, 11852, 12543, 13357, 14769, 16452, 19431, 23228, 30481, 39499 },
+		{ 10508, 10646, 10755, 10872, 11056, 11249, 11594, 11984, 12668, 13483, 14885, 16558, 19523, 23300, 30508, 39500 },
+		{ 10619, 10755, 10871, 10990, 11165, 11365, 11700, 12097, 12776, 13586, 14980, 16643, 19601, 23359, 30533, 39501 },
+		{ 10733, 10872, 10990, 11105, 11271, 11476, 11803, 12204, 12882, 13680, 15073, 16729, 19676, 23419, 30559, 39503 },
+		{ 10915, 11056, 11165, 11271, 11449, 11657, 11979, 12371, 13045, 13842, 15219, 16862, 19799, 23517, 30606, 39513 },
+		{ 11109, 11249, 11365, 11476, 11657, 11855, 12172, 12564, 13233, 14023, 15389, 17018, 19935, 23632, 30665, 39517 },
+		{ 11457, 11594, 11700, 11803, 11979, 12172, 12519, 12895, 13563, 14338, 15691, 17244, 20186, 23839, 30781, 39535 },
+		{ 11852, 11984, 12097, 12204, 12371, 12564, 12895, 13268, 13927, 14695, 16028, 17520, 20469, 24080, 30926, 39567 },
+		{ 12543, 12668, 12776, 12882, 13045, 13233, 13563, 13927, 14575, 15324, 16629, 18080, 20977, 24523, 31218, 39649 },
+		{ 13357, 13483, 13586, 13680, 13842, 14023, 14338, 14695, 15324, 16067, 17258, 18756, 21599, 25072, 31613, 39810 },
+		{ 14769, 14885, 14980, 15073, 15219, 15389, 15691, 16028, 16629, 17258, 18497, 19984, 22737, 26094, 32422, 40256 },
+		{ 16452, 16558, 16643, 16729, 16862, 17018, 17244, 17520, 18080, 18756, 19984, 21436, 24097, 27224, 33479, 40993 },
+		{ 19431, 19523, 19601, 19676, 19799, 19935, 20186, 20469, 20977, 21599, 22737, 24097, 26557, 29604, 35584, 42710 },
+		{ 23228, 23300, 23359, 23419, 23517, 23632, 23839, 24080, 24523, 25072, 26094, 27224, 29604, 32602, 38145, 45081 },
+		{ 30481, 30508, 30533, 30559, 30606, 30665, 30781, 30926, 31218, 31613, 32422, 33479, 35584, 38145, 43319, 49733 },
+		{ 39499, 39500, 39501, 39503, 39513, 39517, 39535, 39567, 39649, 39810, 40256, 40993, 42710, 45081, 49733, 55763 }
+	},
+	{
+		{ 14634, 14757, 14857, 14958, 15123, 15302, 15616, 15979, 16613, 17288, 18591, 20206, 23228, 26871, 34097, 42635 },
+		{ 14757, 14884, 14980, 15084, 15245, 15426, 15735, 16094, 16724, 17379, 18689, 20295, 23300, 26918, 34108, 42636 },
+		{ 14857, 14980, 15085, 15187, 15342, 15526, 15831, 16188, 16820, 17460, 18770, 20370, 23359, 26957, 34125, 42637 },
+		{ 14958, 15084, 15187, 15285, 15445, 15625, 15932, 16280, 16907, 17548, 18851, 20446, 23419, 26998, 34139, 42639 },
+		{ 15123, 15245, 15342, 15445, 15605, 15784, 16087, 16431, 17045, 17683, 18983, 20567, 23517, 27066, 34164, 42641 },
+		{ 15302, 15426, 15526, 15625, 15784, 15961, 16261, 16600, 17183, 17843, 19129, 20703, 23632, 27149, 34193, 42643 },
+		{ 15616, 15735, 15831, 15932, 16087, 16261, 16573, 16899, 17414, 18124, 19396, 20952, 23839, 27307, 34258, 42647 },
+		{ 15979, 16094, 16188, 16280, 16431, 16600, 16899, 17198, 17732, 18443, 19695, 21231, 24080, 27498, 34345, 42662 },
+		{ 16613, 16724, 16820, 16907, 17045, 17183, 17414, 17732, 18313, 19011, 20233, 21736, 24523, 27863, 34544, 42695 },
+		{ 17288, 17379, 17460, 17548, 17683, 17843, 18124, 18443, 19011, 19696, 20884, 22352, 25072, 28332, 34841, 42772 },
+		{ 18591, 18689, 18770, 18851, 18983, 19129, 19396, 19695, 20233, 20884, 22076, 23485, 26094, 29241, 35499, 43067 },
+		{ 20206, 20295, 20370, 20446, 20567, 20703, 20952, 21231, 21736, 22352, 23485, 24840, 27224, 30386, 36330, 43638 },
+		{ 23228, 23300, 23359, 23419, 23517, 23632, 23839, 24080, 24523, 25072, 26094, 27224, 29604, 32602, 38145, 45081 },
+		{ 26871, 26918, 26957, 26998, 27066, 27149, 27307, 27498, 27863, 28332, 29241, 30386, 32602, 35434, 40696, 47134 },
+		{ 34097, 34108, 34125, 34139, 34164, 34193, 34258, 34345, 34544, 34841, 35499, 36330, 38145, 40696, 45450, 51542 },
+		{ 42635, 42636, 42637, 42639, 42641, 42643, 42647, 42662, 42695, 42772, 43067, 43638, 45081, 47134, 51542, 56999 }
+	},
+	{
+		{ 22931, 23015, 23082, 23156, 23272, 23400, 23639, 23925, 24440, 25084, 26237, 27628, 30481, 34097, 40524, 48336 },
+		{ 23015, 23101, 23168, 23243, 23354, 23486, 23718, 24004, 24512, 25149, 26297, 27677, 30508, 34108, 40527, 48337 },
+		{ 23082, 23168, 23243, 23315, 23425, 23554, 23786, 24070, 24574, 25207, 26344, 27719, 30533, 34125, 40529, 48338 },
+		{ 23156, 23243, 23315, 23389, 23493, 23625, 23851, 24134, 24637, 25267, 26391, 27762, 30559, 34139, 40534, 48339 },
+		{ 23272, 23354, 23425, 23493, 23608, 23741, 23965, 24240, 24740, 25362, 26463, 27833, 30606, 34164, 40537, 48340 },
+		{ 23400, 23486, 23554, 23625, 23741, 23871, 24094, 24361, 24857, 25474, 26571, 27915, 30665, 34193, 40540, 48341 },
+		{ 23639, 23718, 23786, 23851, 23965, 24094, 24328, 24587, 25071, 25673, 26692, 28071, 30781, 34258, 40553, 48342 },
+		{ 23925, 24004, 24070, 24134, 24240, 24361, 24587, 24845, 25320, 25905, 26891, 28260, 30926, 34345, 40575, 48344 },
+		{ 24440, 24512, 24574, 24637, 24740, 24857, 25071, 25320, 25781, 26331, 27289, 28624, 31218, 34544, 40634, 48348 },
+		{ 25084, 25149, 25207, 25267, 25362, 25474, 25673, 25905, 26331, 26808, 27805, 29098, 31613, 34841, 40758, 48353 },
+		{ 26237, 26297, 26344, 26391, 26463, 26571, 26692, 26891, 27289, 27805, 28804, 30030, 32422, 35499, 41131, 48449 },
+		{ 27628, 27677, 27719, 27762, 27833, 27915, 28071, 28260, 28624, 29098, 30030, 31211, 33479, 36330, 41775, 48724 },
+		{ 30481, 30508, 30533, 30559, 30606, 30665, 30781, 30926, 31218, 31613, 32422, 33479, 35584, 38145, 43319, 49733 },
+		{ 34097, 34108, 34125, 34139, 34164, 34193, 34258, 34345, 34544, 34841, 35499, 36330, 38145, 40696, 45450, 51542 },
+		{ 40524, 40527, 40529, 40534, 40537, 40540, 40553, 40575, 40634, 40758, 41131, 41775, 43319, 45450, 49971, 55452 },
+		{ 48336, 48337, 48338, 48339, 48340, 48341, 48342, 48344, 48348, 48353, 48449, 48724, 49733, 51542, 55452, 60505 }
+	},
+	{
+		{ 34469, 34480, 34493, 34509, 34538, 34570, 34646, 34754, 34992, 35355, 36119, 37143, 39499, 42635, 48336, 55377 },
+		{ 34480, 34500, 34509, 34526, 34556, 34585, 34662, 34771, 35007, 35366, 36126, 37144, 39500, 42636, 48337, 55377 },
+		{ 34493, 34509, 34524, 34541, 34568, 34601, 34675, 34781, 35020, 35381, 36134, 37151, 39501, 42637, 48338, 55378 },
+		{ 34509, 34526, 34541, 34561, 34587, 34620, 34692, 34798, 35034, 35392, 36142, 37157, 39503, 42639, 48339, 55378 },
+		{ 34538, 34556, 34568, 34587, 34613, 34649, 34720, 34823, 35060, 35414, 36154, 37166, 39513, 42641, 48340, 55379 },
+		{ 34570, 34585, 34601, 34620, 34649, 34688, 34758, 34858, 35090, 35441, 36174, 37183, 39517, 42643, 48341, 55379 },
+		{ 34646, 34662, 34675, 34692, 34720, 34758, 34836, 34932, 35162, 35504, 36217, 37216, 39535, 42647, 48342, 55380 },
+		{ 34754, 34771, 34781, 34798, 34823, 34858, 34932, 35038, 35258, 35591, 36270, 37278, 39567, 42662, 48344, 55381 },
+		{ 34992, 35007, 35020, 35034, 35060, 35090, 35162, 35258, 35482, 35795, 36423, 37418, 39649, 42695, 48348, 55382 },
+		{ 35355, 35366, 35381, 35392, 35414, 35441, 35504, 35591, 35795, 36099, 36672, 37647, 39810, 42772, 48353, 55383 },
+		{ 36119, 36126, 36134, 36142, 36154, 36174, 36217, 36270, 36423, 36672, 37321, 38230, 40256, 43067, 48449, 55385 },
+		{ 37143, 37144, 37151, 37157, 37166, 37183, 37216, 37278, 37418, 37647, 38230, 39115, 40993, 43638, 48724, 55407 },
+		{ 39499, 39500, 39501, 39503, 39513, 39517, 39535, 39567, 39649, 39810, 40256, 40993, 42710, 45081, 49733, 55763 },
+		{ 42635, 42636, 42637, 42639, 42641, 42643, 42647, 42662, 42695, 42772, 43067, 43638, 45081, 47134, 51542, 56999 },
+		{ 48336, 48337, 48338, 48339, 48340, 48341, 48342, 48344, 48348, 48353, 48449, 48724, 49733, 51542, 55452, 60505 },
+		{ 55377, 55377, 55378, 55378, 55379, 55379, 55380, 55381, 55382, 55383, 55385, 55407, 55763, 56999, 60505, 65119 }
+	}
+};
+
+uint16 YM2149Emu::ymout5_u16[32][32][32];
+int16 *YM2149Emu::ymout5 = (int16 *)YM2149Emu::ymout5_u16;
+bool YM2149Emu::_tablesBuilt = false;
+
+uint16 YM2149Emu::mergeVoice(uint16 c, uint16 b, uint16 a) {
+	return (uint16)((c << 10) | (b << 5) | a);
+}
+
+void YM2149Emu::envBuild() {
+	for (int env = 0; env < 16; env++) {
+		for (int block = 0; block < 3; block++) {
+			int vol = 0;
+			int inc = 0;
+			switch (YmEnvDef[env][block]) {
+			case ENV_GODOWN:
+				vol = 31;
+				inc = -1;
+				break;
+			case ENV_GOUP:
+				vol = 0;
+				inc = 1;
+				break;
+			case ENV_DOWN:
+				vol = 0;
+				inc = 0;
+				break;
+			case ENV_UP:
+				vol = 31;
+				inc = 0;
+				break;
+			default:
+				vol = 0;
+				inc = 0;
+				break;
+			}
+
+			for (int i = 0; i < 32; i++) {
+				YmEnvWaves[env][block * 32 + i] = mergeVoice((uint16)vol, (uint16)vol, (uint16)vol);
+				vol += inc;
+			}
+		}
+	}
+}
+
+void YM2149Emu::interpolateVolumetable(uint16 volumetable[32][32][32]) {
+	for (int i = 1; i < 32; i += 2) {
+		for (int j = 1; j < 32; j += 2) {
+			for (int k = 1; k < 32; k += 2) {
+				volumetable[i][j][k] = volumeTable[(i - 1) / 2][(j - 1) / 2][(k - 1) / 2];
+			}
+			volumetable[i][j][0] = volumetable[i][j][1];
+			volumetable[i][j][1] = volumetable[i][j][3];
+			volumetable[i][j][3] = (uint16)(0.5 + sqrt((double)volumetable[i][j][1] * (double)volumetable[i][j][5]));
+			for (int k = 2; k < 32; k += 2)
+				volumetable[i][j][k] = (uint16)(0.5 + sqrt((double)volumetable[i][j][k - 1] * (double)volumetable[i][j][k + 1]));
+		}
+		for (int k = 0; k < 32; k++) {
+			volumetable[i][0][k] = volumetable[i][1][k];
+			volumetable[i][1][k] = volumetable[i][3][k];
+			volumetable[i][3][k] = (uint16)(0.5 + sqrt((double)volumetable[i][1][k] * (double)volumetable[i][5][k]));
+		}
+		for (int j = 2; j < 32; j += 2) {
+			for (int k = 0; k < 32; k++)
+				volumetable[i][j][k] = (uint16)(0.5 + sqrt((double)volumetable[i][j - 1][k] * (double)volumetable[i][j + 1][k]));
+		}
+	}
+
+	for (int j = 0; j < 32; j++) {
+		for (int k = 0; k < 32; k++) {
+			volumetable[0][j][k] = volumetable[1][j][k];
+			volumetable[1][j][k] = volumetable[3][j][k];
+			volumetable[3][j][k] = (uint16)(0.5 + sqrt((double)volumetable[1][j][k] * (double)volumetable[5][j][k]));
+		}
+	}
+
+	for (int i = 2; i < 32; i += 2) {
+		for (int j = 0; j < 32; j++) {
+			for (int k = 0; k < 32; k++)
+				volumetable[i][j][k] = (uint16)(0.5 + sqrt((double)volumetable[i - 1][j][k] * (double)volumetable[i + 1][j][k]));
+		}
+	}
+}
+
+void YM2149Emu::normalise5bitTable(uint16 *in5bit, int16 *out5bit, unsigned int level) {
+	unsigned int minv = 0xffffffffu;
+	unsigned int maxv = 0;
+
+	for (int i = 0; i < 32 * 32 * 32; i++) {
+		unsigned int v = in5bit[i];
+		if (v < minv)
+			minv = v;
+		if (v > maxv)
+			maxv = v;
+	}
+
+	const double scale = (maxv != minv) ? ((double)level / (double)(maxv - minv)) : 1.0;
+	for (int i = 0; i < 32 * 32 * 32; i++) {
+		double v = (double)in5bit[i];
+		v = (v - (double)minv) * scale;
+		int iv = (int)lround(v);
+		if (iv > 32767)
+			iv = 32767;
+		if (iv < -32768)
+			iv = -32768;
+		out5bit[i] = (int16)iv;
+	}
+}
+
+void YM2149Emu::initOnce() {
+	if (_tablesBuilt)
+		return;
+	_tablesBuilt = true;
+
+	envBuild();
+	interpolateVolumetable(ymout5_u16);
+	normalise5bitTable(&ymout5_u16[0][0][0], ymout5, 0x7fff);
+}
+
+uint16 YM2149Emu::tonePer(uint8 rHigh, uint8 rLow) {
+	uint16 per = (uint16)(((uint16)rHigh & 0x0f) << 8) | (uint16)rLow;
+	return per;
+}
+
+uint16 YM2149Emu::noisePer(uint8 rNoise) {
+	return (uint16)(rNoise & 0x1f);
+}
+
+uint16 YM2149Emu::envPer(uint8 rHigh, uint8 rLow) {
+	return (uint16)(((uint16)rHigh << 8) | (uint16)rLow);
+}
+
+uint32 YM2149Emu::rndCompute() {
+	uint32 b = (((_rndRack >> 16) ^ (_rndRack >> 13)) & 1);
+	_rndRack = (_rndRack >> 1) | (b << 16);
+	return (_rndRack & 1) ? 0xffff : 0;
+}
+
+
+void YM2149Emu::setOutputRate(int outputRate) {
+	if (outputRate <= 0)
+		outputRate = 44100;
+	_rate = outputRate;
+
+	_posFractWeightedN = 0;
+	_YMBuffer250PosRead = 0;
+	_YMBuffer250PosWrite = 0;
+}
+
+void YM2149Emu::reset() {
+	memset(_soundRegs, 0, sizeof(_soundRegs));
+
+	_toneAPer = _toneBPer = _toneCPer = 1;
+	_toneACount = _toneBCount = _toneCCount = 0;
+	_toneAVal = _toneBVal = _toneCVal = YM_SQUARE_UP;
+
+	_noisePer = 1;
+	_noiseCount = 0;
+	_noiseVal = 0;
+
+	_envPer = 1;
+	_envCount = 0;
+	_envPos = 0;
+	_envShape = 0;
+
+	_mixerTA = _mixerTB = _mixerTC = 0;
+	_mixerNA = _mixerNB = _mixerNC = 0;
+
+	_rndRack = 1;
+	_freqDiv2 = 0;
+
+	_envMask3Voices = 0;
+	_vol3Voices = 0;
+
+	_posFractWeightedN = 0;
+	_YMBuffer250PosRead = 0;
+	_YMBuffer250PosWrite = 0;
+
+	for (int r = 0; r < ARRAYSIZE(_soundRegs); r++)
+		writeReg(r, 0);
+}
+
+void YM2149Emu::writeReg(int reg, uint8 data) {
+	switch (reg) {
+	case 0:
+		_soundRegs[0] = data;
+		_toneAPer = tonePer(_soundRegs[1], _soundRegs[0]);
+		break;
+	case 1:
+		_soundRegs[1] = data & 0x0f;
+		_toneAPer = tonePer(_soundRegs[1], _soundRegs[0]);
+		break;
+	case 2:
+		_soundRegs[2] = data;
+		_toneBPer = tonePer(_soundRegs[3], _soundRegs[2]);
+		break;
+	case 3:
+		_soundRegs[3] = data & 0x0f;
+		_toneBPer = tonePer(_soundRegs[3], _soundRegs[2]);
+		break;
+	case 4:
+		_soundRegs[4] = data;
+		_toneCPer = tonePer(_soundRegs[5], _soundRegs[4]);
+		break;
+	case 5:
+		_soundRegs[5] = data & 0x0f;
+		_toneCPer = tonePer(_soundRegs[5], _soundRegs[4]);
+		break;
+	case 6:
+		_soundRegs[6] = data & 0x1f;
+		_noisePer = noisePer(_soundRegs[6]);
+		break;
+
+	case 7:
+		_soundRegs[7] = data & 0x3f;
+		_mixerTA = (data & (1 << 0)) ? 0xffff : 0;
+		_mixerTB = (data & (1 << 1)) ? 0xffff : 0;
+		_mixerTC = (data & (1 << 2)) ? 0xffff : 0;
+		_mixerNA = (data & (1 << 3)) ? 0xffff : 0;
+		_mixerNB = (data & (1 << 4)) ? 0xffff : 0;
+		_mixerNC = (data & (1 << 5)) ? 0xffff : 0;
+		break;
+
+	case 8:
+		_soundRegs[8] = data & 0x1f;
+		if (data & 0x10) {
+			_envMask3Voices |= YM_MASK_A;
+			_vol3Voices &= ~YM_MASK_A;
+		} else {
+			_envMask3Voices &= ~YM_MASK_A;
+			_vol3Voices &= ~YM_MASK_A;
+			_vol3Voices |= YmVolume4to5[_soundRegs[8]];
+		}
+		break;
+
+	case 9:
+		_soundRegs[9] = data & 0x1f;
+		if (data & 0x10) {
+			_envMask3Voices |= YM_MASK_B;
+			_vol3Voices &= ~YM_MASK_B;
+		} else {
+			_envMask3Voices &= ~YM_MASK_B;
+			_vol3Voices &= ~YM_MASK_B;
+			_vol3Voices |= (YmVolume4to5[_soundRegs[9]]) << 5;
+		}
+		break;
+
+	case 10:
+		_soundRegs[10] = data & 0x1f;
+		if (data & 0x10) {
+			_envMask3Voices |= YM_MASK_C;
+			_vol3Voices &= ~YM_MASK_C;
+		} else {
+			_envMask3Voices &= ~YM_MASK_C;
+			_vol3Voices &= ~YM_MASK_C;
+			_vol3Voices |= (YmVolume4to5[_soundRegs[10]]) << 10;
+		}
+		break;
+
+	case 11:
+		_soundRegs[11] = data;
+		_envPer = envPer(_soundRegs[12], _soundRegs[11]);
+		break;
+
+	case 12:
+		_soundRegs[12] = data;
+		_envPer = envPer(_soundRegs[12], _soundRegs[11]);
+		break;
+
+	case 13:
+		_soundRegs[13] = data & 0x0f;
+		_envPos = 0;
+		_envCount = 0;
+		_envShape = _soundRegs[13];
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+void YM2149Emu::generate(int16 *dst, int count) {
+	if (count <= 0)
+		return;
+
+	const int desired250Unclamped = (int)ceil((double)count * (double)YM_ATARI_CLOCK_COUNTER / (double)_rate) + 32;
+	const int desired250 = (desired250Unclamped < (YM_BUFFER_250_SIZE - 1)) ? desired250Unclamped : (YM_BUFFER_250_SIZE - 1);
+
+	const int available250 = (_YMBuffer250PosWrite - _YMBuffer250PosRead) & YM_BUFFER_250_SIZE_MASK;
+	if (available250 < desired250) {
+		const int missing = desired250 - available250;
+		doSamples250(missing);
+	}
+
+	for (int i = 0; i < count; i++)
+		dst[i] = (int16)nextSample();
+}
+
+
+void YM2149Emu::doSamples250(int samplesToGenerate250) {
+	int pos = _YMBuffer250PosWrite;
+
+	for (int n = 0; n < samplesToGenerate250; n++) {
+		_freqDiv2 ^= 1;
+		if (_freqDiv2 == 0)
+			_noiseCount++;
+
+		if (_noiseCount >= _noisePer) {
+			_noiseCount = 0;
+			_noiseVal = (uint16)rndCompute();
+		}
+
+		_toneACount++;
+		if (_toneACount >= _toneAPer) {
+			_toneACount = 0;
+			_toneAVal ^= YM_SQUARE_UP;
+		}
+
+		_toneBCount++;
+		if (_toneBCount >= _toneBPer) {
+			_toneBCount = 0;
+			_toneBVal ^= YM_SQUARE_UP;
+		}
+
+		_toneCCount++;
+		if (_toneCCount >= _toneCPer) {
+			_toneCCount = 0;
+			_toneCVal ^= YM_SQUARE_UP;
+		}
+
+		_envCount += 1;
+		if (_envCount >= _envPer) {
+			_envCount = 0;
+			_envPos += 1;
+			if ((int)_envPos >= 3 * 32)
+				_envPos -= 2 * 32;
+		}
+
+		uint16 env3 = YmEnvWaves[_envShape][_envPos];
+		env3 &= _envMask3Voices;
+
+		uint32 bt = ((uint32)_toneAVal | _mixerTA) & ((uint32)_noiseVal | _mixerNA);
+		uint16 tone3 = (uint16)(bt & YM_MASK_1VOICE);
+
+		bt = ((uint32)_toneBVal | _mixerTB) & ((uint32)_noiseVal | _mixerNB);
+		tone3 |= (uint16)((bt & YM_MASK_1VOICE) << 5);
+
+		bt = ((uint32)_toneCVal | _mixerTC) & ((uint32)_noiseVal | _mixerNC);
+		tone3 |= (uint16)((bt & YM_MASK_1VOICE) << 10);
+
+		tone3 &= (uint16)(env3 | _vol3Voices);
+
+		_YMBuffer250[pos] = ymout5[(int)tone3];
+		pos = (pos + 1) & YM_BUFFER_250_SIZE_MASK;
+	}
+
+	_YMBuffer250PosWrite = pos;
+}
+
+int16 YM2149Emu::nextSample() {
+	const uint32 intervalFract = (uint32)(((uint64)YM_ATARI_CLOCK_COUNTER * 0x10000ULL) / (uint64)_rate);
+	int64 total = 0;
+
+	if (((_YMBuffer250PosWrite - _YMBuffer250PosRead) & YM_BUFFER_250_SIZE_MASK) == 0) {
+		_posFractWeightedN = 0;
+		return 0;
+	}
+
+	if (_posFractWeightedN) {
+		total += ((int64)_YMBuffer250[_YMBuffer250PosRead]) * (int64)(0x10000 - _posFractWeightedN);
+		_YMBuffer250PosRead = (_YMBuffer250PosRead + 1) & YM_BUFFER_250_SIZE_MASK;
+		_posFractWeightedN -= 0x10000;
+
+		if (((_YMBuffer250PosWrite - _YMBuffer250PosRead) & YM_BUFFER_250_SIZE_MASK) == 0) {
+			_posFractWeightedN = 0;
+			return 0;
+		}
+	}
+
+	_posFractWeightedN += intervalFract;
+
+	while (_posFractWeightedN & 0xffff0000) {
+		total += ((int64)_YMBuffer250[_YMBuffer250PosRead]) * 0x10000;
+		_YMBuffer250PosRead = (_YMBuffer250PosRead + 1) & YM_BUFFER_250_SIZE_MASK;
+		_posFractWeightedN -= 0x10000;
+
+		if (((_YMBuffer250PosWrite - _YMBuffer250PosRead) & YM_BUFFER_250_SIZE_MASK) == 0) {
+			_posFractWeightedN = 0;
+			return 0;
+		}
+	}
+
+	if (_posFractWeightedN) {
+		total += ((int64)_YMBuffer250[_YMBuffer250PosRead]) * (int64)_posFractWeightedN;
+	}
+
+	return (int16)(total / (int64)intervalFract);
+}
+
+
+void YM2149Emu::generateSamples(int16 *buffer, int numSamples) {
+	generate(buffer, numSamples);
+}
+
+} // End of namespace Audio
diff --git a/audio/softsynth/ym2149.h b/audio/softsynth/ym2149.h
new file mode 100644
index 00000000000..086d5179e74
--- /dev/null
+++ b/audio/softsynth/ym2149.h
@@ -0,0 +1,115 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AUDIO_SOFTSYNTH_YM2149_H
+#define AUDIO_SOFTSYNTH_YM2149_H
+
+#include "audio/ym2149.h"
+
+namespace Audio {
+
+class YM2149Emu final : public YM2149::YM2149, public EmulatedChip {
+public:
+	YM2149Emu();
+	~YM2149Emu() override;
+
+	bool init() override;
+	void reset() override;
+	void writeReg(int reg, uint8 value) override;
+	bool isStereo() const override { return false; }
+
+protected:
+	void generateSamples(int16 *buffer, int numSamples) override;
+
+private:
+	static const int YM_ATARI_CLOCK = 2000000;
+	static const int YM_ATARI_CLOCK_COUNTER = (YM_ATARI_CLOCK / 8);
+
+	static const int YM_BUFFER_250_SIZE = 32768;
+	static const int YM_BUFFER_250_SIZE_MASK = (YM_BUFFER_250_SIZE - 1);
+
+	static const uint32 YmVolume4to5[32];
+
+	static const int ENV_GODOWN = 0;
+	static const int ENV_GOUP = 1;
+	static const int ENV_DOWN = 2;
+	static const int ENV_UP = 3;
+	static const int YmEnvDef[16][3];
+
+	static uint16 YmEnvWaves[16][32 * 3];
+	static const uint16 volumeTable[16][16][16];
+	static uint16 ymout5_u16[32][32][32];
+	static int16 *ymout5;
+	static bool _tablesBuilt;
+
+	static const uint16 YM_MASK_1VOICE = 0x1f;
+	static const uint16 YM_MASK_A = 0x1f;
+	static const uint16 YM_MASK_B = (0x1f << 5);
+	static const uint16 YM_MASK_C = (0x1f << 10);
+
+	static const uint16 YM_SQUARE_UP = 0x1f;
+	static const uint16 YM_SQUARE_DOWN = 0x00;
+
+	uint16 _toneAPer, _toneACount, _toneAVal;
+	uint16 _toneBPer, _toneBCount, _toneBVal;
+	uint16 _toneCPer, _toneCCount, _toneCVal;
+	uint16 _noisePer, _noiseCount, _noiseVal;
+	uint16 _envPer, _envCount;
+
+	uint32 _envPos;
+	int _envShape;
+
+	uint32 _mixerTA, _mixerTB, _mixerTC;
+	uint32 _mixerNA, _mixerNB, _mixerNC;
+
+	uint32 _rndRack;
+	uint16 _freqDiv2;
+
+	uint16 _envMask3Voices;
+	uint16 _vol3Voices;
+
+	uint8 _soundRegs[14];
+
+	int16 _YMBuffer250[YM_BUFFER_250_SIZE];
+	int _YMBuffer250PosWrite;
+	int _YMBuffer250PosRead;
+
+	uint32 _posFractWeightedN;
+	int _rate;
+
+	void setOutputRate(int outputRate);
+	void generate(int16 *dst, int count);
+	static uint16 mergeVoice(uint16 c, uint16 b, uint16 a);
+	static void envBuild();
+	static void interpolateVolumetable(uint16 volumetable[32][32][32]);
+	static void normalise5bitTable(uint16 *in5bit, int16 *out5bit, unsigned int level);
+	static void initOnce();
+	static uint16 tonePer(uint8 rHigh, uint8 rLow);
+	static uint16 noisePer(uint8 rNoise);
+	static uint16 envPer(uint8 rHigh, uint8 rLow);
+	uint32 rndCompute();
+	void doSamples250(int samplesToGenerate250);
+	int16 nextSample();
+};
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/ym2149.cpp b/audio/ym2149.cpp
new file mode 100644
index 00000000000..c0403d8a165
--- /dev/null
+++ b/audio/ym2149.cpp
@@ -0,0 +1,45 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "audio/ym2149.h"
+#include "audio/softsynth/ym2149.h"
+
+#include "common/textconsole.h"
+
+namespace YM2149 {
+
+YM2149 *Config::create() {
+	return new Audio::YM2149Emu();
+}
+
+bool YM2149::_hasInstance = false;
+
+YM2149::YM2149() {
+	if (_hasInstance)
+		error("There are multiple YM2149 output instances running.");
+	_hasInstance = true;
+}
+
+YM2149::~YM2149() {
+	_hasInstance = false;
+}
+
+} // End of namespace YM2149
diff --git a/audio/ym2149.h b/audio/ym2149.h
new file mode 100644
index 00000000000..8404ed2a066
--- /dev/null
+++ b/audio/ym2149.h
@@ -0,0 +1,71 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AUDIO_YM2149_H
+#define AUDIO_YM2149_H
+
+#include "audio/chip.h"
+#include "common/scummsys.h"
+
+namespace YM2149 {
+
+class YM2149;
+
+class Config {
+public:
+	/**
+	 * Creates a YM2149 driver.
+	 */
+	static YM2149 *create();
+};
+
+class YM2149 : virtual public Audio::Chip {
+private:
+	static bool _hasInstance;
+
+public:
+	YM2149();
+	virtual ~YM2149();
+
+	/**
+	 * Initializes the YM2149 emulator.
+	 *
+	 * @return		true on success, false on failure
+	 */
+	virtual bool init() = 0;
+
+	/**
+	 * Reinitializes the YM2149 emulator
+	 */
+	virtual void reset() = 0;
+
+	/**
+	 * Function to directly write to a specific YM2149 register.
+	 *
+	 * @param r		hardware register number to write to
+	 * @param v		value, which will be written
+	 */
+	virtual void writeReg(int r, uint8 v) = 0;
+};
+
+} // End of namespace YM2149
+
+#endif
diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 2103481eebc..2e9154ff5db 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -156,6 +156,10 @@ Common::Error AGOSEngine_Elvira1::init() {
 		else
 			error("AGOSEngine_Elvira1::init(): Failed to load SJIS font.");
 	}
+	// Automatically start the Atari ST music driver with tune 1
+	if (getPlatform() == Common::kPlatformAtariST && (getFeatures() & GF_DEMO)) {
+		playMusic(1, 0);
+	}
 	return ret;
 }
 
diff --git a/engines/agos/agos.h b/engines/agos/agos.h
index 950459d2b4d..509775617e1 100644
--- a/engines/agos/agos.h
+++ b/engines/agos/agos.h
@@ -69,6 +69,8 @@ class SeekableAudioStream;
 
 namespace AGOS {
 
+class ElviraAtariSTPlayer;
+
 enum {
 	kDebugOpcode = 1,
 	kDebugVGAOpcode,
@@ -625,6 +627,7 @@ protected:
 	Audio::SoundHandle _modHandle;
 	Audio::SoundHandle _digitalMusicHandle;
 	Audio::SeekableAudioStream *_digitalMusicStream = nullptr;
+	ElviraAtariSTPlayer *_elviraAtariSTPlayer = nullptr;
 
 	Sound *_sound;
 
diff --git a/engines/agos/drivers/elvira_atarist.cpp b/engines/agos/drivers/elvira_atarist.cpp
new file mode 100644
index 00000000000..d19e6af6a72
--- /dev/null
+++ b/engines/agos/drivers/elvira_atarist.cpp
@@ -0,0 +1,1018 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/agos/drivers/elvira_atarist.h"
+
+#include "audio/ym2149.h"
+#include "common/func.h"
+
+#include "common/endian.h"
+#include "common/textconsole.h"
+#include "common/util.h"
+#include "common/debug.h"
+
+namespace AGOS {
+
+
+struct ElviraPrgTosImage {
+	Common::Array<uint8> mem;
+	uint32 textSize = 0;
+	uint32 dataSize = 0;
+	uint32 bssSize = 0;
+	uint32 symSize = 0;
+	uint32 memSize = 0;
+
+
+	bool load(const Common::Array<uint8> &prgBytes) {
+		if (prgBytes.size() < 28)
+			return false;
+		if (READ_BE_UINT16(prgBytes.begin()) != 0x601A)
+			return false;
+
+		textSize = READ_BE_UINT32(prgBytes.begin() + 2);
+		dataSize = READ_BE_UINT32(prgBytes.begin() + 6);
+		bssSize = READ_BE_UINT32(prgBytes.begin() + 10);
+		symSize = READ_BE_UINT32(prgBytes.begin() + 14);
+
+		const uint32 offText = 28;
+		const uint32 offData = offText + textSize;
+		const uint32 offSym = offData + dataSize;
+		const uint32 offRel = offSym + symSize;
+		if (prgBytes.size() < offRel + 4)
+			return false;
+
+		memSize = textSize + dataSize + bssSize;
+		mem.resize(memSize);
+		memset(mem.begin(), 0, memSize);
+		if (textSize)
+			memcpy(&mem[0], prgBytes.begin() + offText, textSize);
+		if (dataSize)
+			memcpy(&mem[0] + textSize, prgBytes.begin() + offData, dataSize);
+
+		const uint8 *rel = prgBytes.begin() + offRel;
+		const uint32 relSize = (uint32)prgBytes.size() - offRel;
+
+		uint32 ofs = READ_BE_UINT32(rel);
+		uint32 pos = 4;
+
+		auto applyAt = [&](uint32 addr) {
+			if (addr + 4 > mem.size())
+				return;
+			uint32 v = READ_BE_UINT32(&mem[addr]);
+			WRITE_BE_UINT32(&mem[addr], v);
+		};
+
+		if (ofs != 0)
+			applyAt(ofs);
+
+		while (pos < relSize) {
+			uint8 b = rel[pos++];
+			if (b == 0)
+				break;
+			if (b == 1) {
+				ofs += 254;
+				continue;
+			}
+			ofs += b;
+			applyAt(ofs);
+		}
+
+		return true;
+	}
+};
+
+struct ElviraPrgLabels {
+	uint32 L003A = 0, L003B = 0, L003C = 0, L003D = 0, L003E = 0, L003F = 0, L0040 = 0, L0041 = 0;
+	uint32 tunetab = 0, L0051 = 0, L004B = 0, L0067 = 0, L0068 = 0;
+	uint32 L0042 = 0, L0043 = 0, L0044 = 0, L0045 = 0, L0046 = 0, L0047 = 0, L0048 = 0, L0049 = 0, L004A = 0;
+	uint32 L004C = 0, L004D = 0, L004E = 0, L004F = 0;
+};
+
+static uint32 findBytePattern(const Common::Array<uint8> &mem, const uint8 *needle, uint32 needleLen, uint32 start) {
+	for (uint32 i = start; i + needleLen <= mem.size(); ++i) {
+		if (memcmp(&mem[i], needle, needleLen) == 0)
+			return i;
+	}
+	return 0;
+}
+
+static uint32 findTuneTable(const Common::Array<uint8> &mem) {
+	for (uint32 off = 0x100; off + 7 * 16 <= mem.size(); off += 2) {
+		bool ok = true;
+		for (uint32 e = 0; e < 7; ++e) {
+			uint32 base = off + e * 16;
+			uint32 p0 = READ_BE_UINT32(&mem[base + 0]);
+			uint32 p1 = READ_BE_UINT32(&mem[base + 4]);
+			uint32 p2 = READ_BE_UINT32(&mem[base + 8]);
+			if (mem[base + 12] || mem[base + 13] || mem[base + 14] || mem[base + 15]) {
+				ok = false;
+				break;
+			}
+			if (p0 < 0x100 || p1 < 0x100 || p2 < 0x100 || p0 >= mem.size() || p1 >= mem.size() || p2 >= mem.size()) {
+				ok = false;
+				break;
+			}
+		}
+		if (ok)
+			return off;
+	}
+	return 0;
+}
+
+
+static bool applyKnownElviraDriverLayout(uint32 textSize, ElviraPrgLabels &L) {
+	// Full game
+	if (textSize == 0x15920) {
+		L.L003C = 0xDF6A;
+		L.L003D = 0xDFFE;
+		L.L003E = 0xE004;
+		L.L003F = 0xE00A;
+		L.L0040 = 0xE016;
+		L.L0041 = 0xE022;
+		L.L0042 = 0xE032;
+		L.L0043 = 0xE04A;
+		L.L0044 = 0xE050;
+		L.L0045 = 0xE056;
+		L.L0046 = 0xE05C;
+		L.L0047 = 0xE0B0;
+		L.L0048 = 0xE0B2;
+		L.L0049 = 0xE0B6;
+		L.L004A = 0xE0B8;
+		L.L004B = 0xE0C0;
+		L.L004C = 0xE0C6;
+		L.L004D = 0xE0CC;
+		L.L004E = 0xE0D3;
+		L.L004F = 0xE0DE;
+		L.L003A = L.L003C - 64;
+		L.L003B = L.L003C - 62;
+		L.tunetab = 0xE114;
+		L.L0051 = 0xE184;
+		L.L0067 = 0xE5E8;
+		L.L0068 = 0xE652;
+		return true;
+	}
+
+	// Demo
+	if (textSize == 0x0A4A6) {
+		L.L003C = 0x686C;
+		L.L003D = 0x6900;
+		L.L003E = 0x6906;
+		L.L003F = 0x690C;
+		L.L0040 = 0x6918;
+		L.L0041 = 0x6924;
+		L.L0042 = 0x6934;
+		L.L0043 = 0x694C;
+		L.L0044 = 0x6952;
+		L.L0045 = 0x6958;
+		L.L0046 = 0x695E;
+		L.L0047 = 0x69B2;
+		L.L0048 = 0x69B4;
+		L.L0049 = 0x69B8;
+		L.L004A = 0x69BA;
+		L.L004B = 0x69C2;
+		L.L004C = 0x69C8;
+		L.L004D = 0x69CE;
+		L.L004E = 0x69D5;
+		L.L004F = 0x69E0;
+		L.L003A = L.L003C - 64;
+		L.L003B = L.L003C - 62;
+		L.tunetab = 0x6A16;
+		L.L0051 = 0x6A86;
+		L.L0067 = 0x6EEA;
+		L.L0068 = 0x6F54;
+		return true;
+	}
+
+	return false;
+}
+
+
+static bool locateEmbeddedDriverLayout(const Common::Array<uint8> &mem, ElviraPrgLabels &L, uint32 textSize) {
+	static const uint8 sigL0068[] = {0xFF,0xFF,0x00,0x01,0x00,0xA0,0xFF,0xFF,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x14};
+	static const uint8 sigL004B[] = {0x00,0x00,0x00,0x1A,0x00,0x34};
+	static const uint8 sigL003C[] = {0x0D,0x60,0x0C,0xA0,0x0B,0xE8,0x0B,0x40};
+	static const uint8 sigL003D[] = {0x00,0x01,0x00,0x02,0x00,0x04};
+	static const uint8 sigL003E[] = {0x00,0x01,0x00,0x01,0x00,0x01};
+	static const uint8 sigL0067[] = {0x00,0x02,0x00,0x08,0x00,0x12,0x00,0x1C};
+	static const uint8 sigL0045[] = {0x00,0x00,0x00,0x1C,0x00,0x38};
+
+	L.L0068 = findBytePattern(mem, sigL0068, sizeof(sigL0068), 0x100);
+	L.L004B = findBytePattern(mem, sigL004B, sizeof(sigL004B), 0x100);
+	L.L003C = findBytePattern(mem, sigL003C, sizeof(sigL003C), 0x100);
+	L.L003D = findBytePattern(mem, sigL003D, sizeof(sigL003D), L.L003C ? L.L003C : 0x100);
+	L.L003E = findBytePattern(mem, sigL003E, sizeof(sigL003E), L.L003D ? L.L003D : 0x100);
+	L.L0067 = findBytePattern(mem, sigL0067, sizeof(sigL0067), 0x100);
+	L.L0045 = findBytePattern(mem, sigL0045, sizeof(sigL0045), L.L003E ? L.L003E : 0x100);
+	L.tunetab = findTuneTable(mem);
+
+	if (!L.L0068 || !L.L004B || !L.L003C || !L.L003D || !L.L003E || !L.L0067 || !L.L0045 || !L.tunetab)
+		return applyKnownElviraDriverLayout(textSize, L);
+
+	if (L.L003C >= 64) {
+		L.L003A = L.L003C - 64;
+		L.L003B = L.L003C - 62;
+	}
+
+	L.L003F = L.L003E + 6;
+	L.L0040 = L.L003F + 12;
+	L.L0041 = L.L0040 + 12;
+	L.L0051 = L.tunetab + 7 * 16;
+
+	L.L0042 = L.L0045 - 0x24;
+	L.L0043 = L.L0042 + 0x18;
+	L.L0044 = L.L0043 + 0x06;
+	L.L0046 = L.L0045 + 0x06;
+	L.L0047 = L.L0046 + 0x54;
+	L.L0048 = L.L0047 + 0x02;
+	L.L0049 = L.L0048 + 0x04;
+	L.L004A = L.L0049 + 0x02;
+	L.L004C = L.L004B + 0x06;
+	L.L004D = L.L004C + 0x06;
+	L.L004E = L.L004C + 0x0D;
+	L.L004F = L.L004C + 0x18;
+
+	return true;
+}
+
+
+static inline uint16 addByteToLowWord(uint16 w, uint8 b) {
+	uint8 lo = (uint8)(w & 0xFF);
+	lo = (uint8)(lo + b);
+	return (uint16)((w & 0xFF00) | lo);
+}
+
+static inline uint16 shiftLeft16(uint16 v, unsigned n) {
+	n &= 15;
+	return (uint16)(v << n);
+}
+
+static inline uint16 rotateLeft16(uint16 v, unsigned n) {
+	n &= 15;
+	return (uint16)((v << n) | (v >> (16 - n)));
+}
+
+static inline uint16 updateMixerWordForVoiceType(uint16 mixerWord, uint16 channelMask, uint8 typeByte) {
+	uint16 d3 = mixerWord;
+	uint16 d2 = channelMask;
+
+	if (typeByte == 1) {
+		d3 = (uint16)(d3 | d2);
+		d2 = shiftLeft16(d2, 3);
+		d2 = (uint16)~d2;
+		d3 = (uint16)(d3 & d2);
+		return d3;
+	}
+
+	if (typeByte == 2) {
+		d2 = (uint16)~d2;
+		d3 = (uint16)(d3 & d2);
+		d2 = rotateLeft16(d2, 3);
+		d3 = (uint16)(d3 & d2);
+		return d3;
+	}
+
+	d2 = (uint16)~d2;
+	d3 = (uint16)(d3 & d2);
+	d2 = (uint16)~d2;
+	d2 = shiftLeft16(d2, 3);
+	d3 = (uint16)(d3 | d2);
+	return d3;
+}
+
+class ElviraPrgDriver {
+public:
+	ElviraPrgDriver(ElviraAtariSTPlayer *owner, const Common::Array<uint8> &prgBytes) : _owner(owner) {
+		ElviraPrgTosImage prg;
+		if (!prg.load(prgBytes)) {
+			warning("AGOS: Failed to load ELVIRA.PRG");
+			return;
+		}
+		_mem = prg.mem;
+		if (!locateEmbeddedDriverLayout(_mem, _labels, prg.textSize)) {
+			warning("AGOS: Failed to locate embedded ELVIRA audio driver");
+			return;
+		}
+		_valid = true;
+	}
+
+	bool isValid() const { return _valid; }
+
+	void init(uint16 tune1Based) {
+		if (!_valid)
+			return;
+		_savedD7 = 0;
+		WRITE_BE_UINT32(&_mem[_labels.L003E], 0x00010001);
+		WRITE_BE_UINT16(&_mem[_labels.L003E + 4], 1);
+		WRITE_BE_UINT32(&_mem[_labels.L0040 + 0], _labels.L0068);
+		WRITE_BE_UINT32(&_mem[_labels.L003F + 0], _labels.L0068);
+		WRITE_BE_UINT32(&_mem[_labels.L0040 + 4], _labels.L0068);
+		WRITE_BE_UINT32(&_mem[_labels.L003F + 4], _labels.L0068);
+		WRITE_BE_UINT32(&_mem[_labels.L0040 + 8], _labels.L0068);
+		WRITE_BE_UINT32(&_mem[_labels.L003F + 8], _labels.L0068);
+
+		uint16 d0w = (uint16)((tune1Based - 1) << 4);
+		uint32 a1 = _labels.tunetab;
+		WRITE_BE_UINT32(&_mem[_labels.L0041 + 0], READ_BE_UINT32(&_mem[a1 + d0w + 0]));
+		WRITE_BE_UINT32(&_mem[_labels.L0041 + 4], READ_BE_UINT32(&_mem[a1 + d0w + 4]));
+		WRITE_BE_UINT32(&_mem[_labels.L0041 + 8], READ_BE_UINT32(&_mem[a1 + d0w + 8]));
+		WRITE_BE_UINT16(&_mem[_labels.L0049], 0xFFFF);
+		WRITE_BE_UINT16(&_mem[_labels.L0048 + 0], 0);
+		WRITE_BE_UINT16(&_mem[_labels.L0048 + 2], 0);
+		WRITE_BE_UINT16(&_mem[_labels.L0047], 0);
+	}
+
+	void vbl() {
+		if (!_valid)
+			return;
+		updateActivePitchSlides_L0017();
+		updatePerChannelVibrato_L0025();
+		updateSequencePitchLayers_L0020();
+		updatePendingMixerRestores_L002B();
+		updateVolumeEnvelopeStages_L002E();
+		updateAutomaticEnvelopeWrites_L0037();
+
+		int16 d7 = 2;
+		uint16 d0 = 0;
+		uint32 a0 = _labels.L003E;
+		while (true) {
+			uint16 v = (uint16)(READ_BE_UINT16(&_mem[a0 + d0]) - 1);
+			WRITE_BE_UINT16(&_mem[a0 + d0], v);
+			if (v == 0)
+				processChannelCommandStream_L0004(d0);
+			d0 = (uint16)(d0 + 2);
+			if (--d7 < 0)
+				break;
+		}
+	}
+
+private:
+	ElviraAtariSTPlayer *_owner;
+	bool _valid = false;
+	Common::Array<uint8> _mem;
+	ElviraPrgLabels _labels;
+	uint8 _savedD7 = 0;
+
+	uint8 read8(uint32 a) const {
+		return _mem[a];
+	}
+	void write8(uint32 a, uint8 v) {
+		_mem[a] = v;
+	}
+	void psgWrite(uint8 reg, uint8 val) {
+		_owner->writeReg((int)reg, val);
+	}
+
+	void processChannelCommandStream_L0004(uint16 d0);
+	void applyImmediateMuteCommand_L0012(uint16 d0, uint16 d5x2, uint16 d6, uint32 &a3);
+	void loadVoiceInstrument_L0013(uint16 d0, uint32 &a3);
+	void setupPitchSlideCommand_L0015(uint16 d0, uint16 d5x2, uint16 d6, uint32 &a3, uint16 &slideArmed);
+	void finalizePitchSlideStep_L0016(uint16 d5x2, uint16 period);
+	void emitNoiseModeNote_L000F(uint16 d0, uint16 d5x2, uint16 d6, uint32 &a3);
+	void setupSequenceTableCommand_L001B(uint16 d0, uint16 d6, uint32 &a3);
+
+	void updateActivePitchSlides_L0017();
+	void updateSequencePitchLayers_L0020();
+	void updatePerChannelVibrato_L0025();
+	void updatePendingMixerRestores_L002B();
+	void updateVolumeEnvelopeStages_L002E();
+	void updateAutomaticEnvelopeWrites_L0037();
+};
+
+void ElviraPrgDriver::processChannelCommandStream_L0004(uint16 d0) {
+	uint16 d5 = d0;
+	uint16 d6 = (uint16)(d0 >> 1);
+	uint16 d5x2 = (uint16)(d5 + d5);
+
+	uint16 slotOffset = READ_BE_UINT16(&_mem[_labels.L004B + d0]);
+	uint16 instrumentOffset = READ_BE_UINT16(&_mem[_labels.L004A + d0]);
+
+	uint32 liveVoice = _labels.L004C;
+	uint32 instrumentBase = _labels.L0051;
+	write8(liveVoice + slotOffset + 1, read8(instrumentBase + instrumentOffset + 1));
+	WRITE_BE_UINT32(&_mem[liveVoice + slotOffset + 6], READ_BE_UINT32(&_mem[instrumentBase + instrumentOffset + 6]));
+	WRITE_BE_UINT16(&_mem[liveVoice + slotOffset + 10], READ_BE_UINT16(&_mem[instrumentBase + instrumentOffset + 10]));
+	write8(liveVoice + slotOffset + 13, read8(instrumentBase + instrumentOffset + 13));
+	WRITE_BE_UINT32(&_mem[liveVoice + slotOffset + 14], READ_BE_UINT32(&_mem[instrumentBase + instrumentOffset + 14]));
+	WRITE_BE_UINT32(&_mem[liveVoice + slotOffset + 18], READ_BE_UINT32(&_mem[instrumentBase + instrumentOffset + 18]));
+	WRITE_BE_UINT16(&_mem[liveVoice + slotOffset + 22], READ_BE_UINT16(&_mem[instrumentBase + instrumentOffset + 22]));
+	write8(liveVoice + slotOffset + 24, read8(instrumentBase + instrumentOffset + 24));
+
+	uint16 seqBase = READ_BE_UINT16(&_mem[_labels.L0045 + d0]);
+	WRITE_BE_UINT16(&_mem[_labels.L0046 + seqBase + 2], 0);
+
+	uint32 patternBlob = _labels.L0068;
+	uint32 patternPtrTable = _labels.L003F;
+	uint32 a3 = READ_BE_UINT32(&_mem[patternPtrTable + d5x2]);
+	uint16 cmd = READ_BE_UINT16(&_mem[a3]);
+	a3 += 2;
+
+	if ((int16)cmd < 0) {
+		uint32 seqCursorTable = _labels.L0040;
+		uint32 seqCursor = READ_BE_UINT32(&_mem[seqCursorTable + d5x2]);
+		uint32 sequenceWord = READ_BE_UINT16(&_mem[seqCursor]);
+		seqCursor += 2;
+
+		if ((uint16)sequenceWord == 0xFFFF) {
+			seqCursor = READ_BE_UINT32(&_mem[_labels.L0041 + d5x2]);
+			sequenceWord = READ_BE_UINT16(&_mem[seqCursor]);
+			seqCursor += 2;
+		}
+
+		write8(_labels.L0048 + d6, 0);
+		if (sequenceWord & 0x8000) {
+			write8(_labels.L0048 + d6, read8(seqCursor + 1));
+			seqCursor += 2;
+			sequenceWord &= 0x7FFF;
+		}
+
+		WRITE_BE_UINT32(&_mem[seqCursorTable + d5x2], seqCursor);
+		uint16 patternOff = READ_BE_UINT16(&_mem[_labels.L0067 + (uint16)(sequenceWord * 2)]);
+		a3 = patternBlob + patternOff;
+		cmd = READ_BE_UINT16(&_mem[a3]);
+		a3 += 2;
+	}
+
+	if (cmd & 0x0001) {
+		applyImmediateMuteCommand_L0012(d0, d5x2, d6, a3);
+		return;
+	}
+	if (cmd & 0x0002)
+		loadVoiceInstrument_L0013(d0, a3);
+	if (cmd & 0x0010)
+		setupSequenceTableCommand_L001B(d0, d6, a3);
+	if (cmd & 0x0008)
+		WRITE_BE_UINT16(&_mem[_labels.L0047], (uint16)(READ_BE_UINT16(&_mem[_labels.L0047]) | 1));
+
+	uint16 slideArmed = 0;
+	if (cmd & 0x0004)
+		setupPitchSlideCommand_L0015(d0, d5x2, d6, a3, slideArmed);
+
+	uint16 wait = READ_BE_UINT16(&_mem[a3]);
+	a3 += 2;
+	WRITE_BE_UINT16(&_mem[_labels.L003E + d0], wait);
+
+	uint16 mask = READ_BE_UINT16(&_mem[_labels.L003D + d0]);
+	uint16 note = READ_BE_UINT16(&_mem[a3]);
+	a3 += 2;
+	if (_labels.L003B)
+		WRITE_BE_UINT16(&_mem[_labels.L003B], note);
+	note = addByteToLowWord(note, read8(_labels.L0048 + d6));
+	write8(_labels.L0044 + d6, (uint8)(note & 0xFF));
+
+	uint16 slot = READ_BE_UINT16(&_mem[_labels.L004B + d0]);
+	note = addByteToLowWord(note, read8(_labels.L004C + slot + 23));
+	uint16 idx = (uint16)((note & 0xFF) * 2);
+	uint16 period = READ_BE_UINT16(&_mem[_labels.L003C + idx]);
+	WRITE_BE_UINT16(&_mem[_labels.L0043 + d0], period);
+
+	if (slideArmed)
+		finalizePitchSlideStep_L0016(d5x2, period);
+
+	if (READ_BE_UINT16(&_mem[_labels.L0047]) != 0) {
+		WRITE_BE_UINT16(&_mem[_labels.L0047], 0);
+		psgWrite((uint8)d0, (uint8)(period & 0xFF));
+		psgWrite((uint8)(d0 + 1), (uint8)(period >> 8));
+		WRITE_BE_UINT32(&_mem[_labels.L003F + d5x2], a3);
+		return;
+	}
+
+	uint8 mode = read8(_labels.L004C + slot + 0);
+	if (mode == 1) {
+		emitNoiseModeNote_L000F(d0, d5x2, d6, a3);
+		return;
+	}
+
+	psgWrite((uint8)d0, (uint8)(period & 0xFF));
+	psgWrite((uint8)(d0 + 1), (uint8)(period >> 8));
+	if (mode == 2)
+		psgWrite(6, read8(_labels.L004C + slot + 12));
+
+	uint16 mixer = READ_BE_UINT16(&_mem[_labels.L0049]);
+	mixer = updateMixerWordForVoiceType(mixer, mask, mode == 2 ? 2 : 0);
+	WRITE_BE_UINT16(&_mem[_labels.L0049], mixer);
+	psgWrite(7, (uint8)(mixer & 0xFF));
+
+	psgWrite((uint8)(8 + d6), read8(_labels.L004C + slot + 1));
+	uint8 shape = read8(_labels.L004C + slot + 5);
+	if (shape != 0) {
+		psgWrite(0x0D, shape);
+		psgWrite(0x0B, read8(_labels.L004C + slot + 3));
+		psgWrite(0x0C, read8(_labels.L004C + slot + 2));
+	}
+
+	WRITE_BE_UINT32(&_mem[_labels.L003F + d5x2], a3);
+}
+
+void ElviraPrgDriver::applyImmediateMuteCommand_L0012(uint16 d0, uint16 d5x2, uint16 d6, uint32 &a3) {
+	uint16 wait = READ_BE_UINT16(&_mem[a3]);
+	a3 += 2;
+	WRITE_BE_UINT16(&_mem[_labels.L003E + d0], wait);
+
+	uint16 mask = READ_BE_UINT16(&_mem[_labels.L003D + d0]);
+	uint16 mixer = READ_BE_UINT16(&_mem[_labels.L0049]);
+	mixer &= (uint16)~mask;
+	WRITE_BE_UINT16(&_mem[_labels.L0049], mixer);
+	psgWrite((uint8)(8 + d6), 0);
+	WRITE_BE_UINT32(&_mem[_labels.L003F + d5x2], a3);
+
+	uint16 slot = READ_BE_UINT16(&_mem[_labels.L004B + d0]);
+	write8(_labels.L004C + slot + 14, 0);
+}
+
+void ElviraPrgDriver::loadVoiceInstrument_L0013(uint16 d0, uint32 &a3) {
+	uint16 instId = READ_BE_UINT16(&_mem[a3]);
+	a3 += 2;
+
+	uint16 d2 = (uint16)(instId * 2);
+	uint16 d4 = d2;
+	uint16 d2a = (uint16)(d2 << 2);
+	uint16 d3 = d2a;
+	d2a = (uint16)(d2a + d2a);
+	d2a = (uint16)(d2a + d3);
+	d2a = (uint16)(d2a + d4);
+
+	WRITE_BE_UINT16(&_mem[_labels.L004A + d0], d2a);
+
+	uint16 slot = READ_BE_UINT16(&_mem[_labels.L004B + d0]);
+	uint32 dst = _labels.L004C + slot;
+	uint32 src = _labels.L0051 + d2a;
+	for (int i = 0; i <= 0x0C; ++i) {
+		WRITE_BE_UINT16(&_mem[dst], READ_BE_UINT16(&_mem[src]));
+		src += 2;
+		dst += 2;
+	}
+
+	uint16 seqBase = READ_BE_UINT16(&_mem[_labels.L0045 + d0]);
+	WRITE_BE_UINT16(&_mem[_labels.L0046 + seqBase + 0], 0);
+}
+
+void ElviraPrgDriver::setupPitchSlideCommand_L0015(uint16 d0, uint16 d5x2, uint16 d6, uint32 &a3, uint16 &slideArmed) {
+	uint8 channelTranspose = read8(_labels.L0048 + d6);
+	uint32 slideState = _labels.L0042;
+	uint32 periodTable = _labels.L003C;
+	uint16 d3 = (uint16)(d5x2 + d5x2);
+
+	uint16 noteWord = READ_BE_UINT16(&_mem[a3]);
+	a3 += 2;
+	noteWord = addByteToLowWord(noteWord, channelTranspose);
+
+	WRITE_BE_UINT16(&_mem[slideState + d3 + 2], READ_BE_UINT16(&_mem[a3]));
+	a3 += 2;
+
+	uint16 idx = (uint16)((noteWord & 0xFF) * 2);
+	uint16 targetPeriod = READ_BE_UINT16(&_mem[periodTable + idx]);
+	WRITE_BE_UINT16(&_mem[slideState + d3 + 4], targetPeriod);
+	WRITE_BE_UINT16(&_mem[slideState + d3 + 0], targetPeriod);
+	WRITE_BE_UINT16(&_mem[slideState + d3 + 6], READ_BE_UINT16(&_mem[a3]));
+	a3 += 2;
+
+	slideArmed = 1;
+}
+
+void ElviraPrgDriver::finalizePitchSlideStep_L0016(uint16 d5x2, uint16 period) {
+	uint32 slideState = _labels.L0042;
+	uint16 d4 = (uint16)(d5x2 + d5x2);
+	uint32 d1u32 = (uint32)READ_BE_UINT16(&_mem[slideState + d4]);
+	int32 dividend = (int32)d1u32 - (int32)((uint32)period & 0xFFFF);
+	int16 divisor = (int16)READ_BE_UINT16(&_mem[slideState + d4 + 2]);
+	int32 quotient = 0;
+	if (divisor != 0)
+		quotient = dividend / (int32)divisor;
+	WRITE_BE_UINT16(&_mem[slideState + d4], (uint16)(quotient & 0xFFFF));
+}
+
+void ElviraPrgDriver::emitNoiseModeNote_L000F(uint16 d0, uint16 d5x2, uint16 d6, uint32 &a3) {
+	uint16 rawNote = _labels.L003B ? READ_BE_UINT16(&_mem[_labels.L003B]) : 0;
+	WRITE_BE_UINT16(&_mem[_labels.L0043 + d0], rawNote);
+	psgWrite(6, (uint8)(rawNote & 0xFF));
+
+	uint16 mask = READ_BE_UINT16(&_mem[_labels.L003D + d0]);
+	uint16 mixer = READ_BE_UINT16(&_mem[_labels.L0049]);
+	mixer |= mask;
+	uint16 d2 = shiftLeft16(mask, 3);
+	d2 = (uint16)~d2;
+	mixer &= d2;
+	WRITE_BE_UINT16(&_mem[_labels.L0049], mixer);
+	psgWrite(7, (uint8)(mixer & 0xFF));
+
+	uint16 slot = READ_BE_UINT16(&_mem[_labels.L004B + d0]);
+	psgWrite((uint8)(8 + d6), read8(_labels.L004C + slot + 1));
+	uint8 shape = read8(_labels.L004C + slot + 5);
+	if (shape != 0) {
+		psgWrite(0x0D, shape);
+		psgWrite(0x0B, read8(_labels.L004C + slot + 3));
+		psgWrite(0x0C, read8(_labels.L004C + slot + 2));
+	}
+
+	WRITE_BE_UINT32(&_mem[_labels.L003F + d5x2], a3);
+}
+
+void ElviraPrgDriver::setupSequenceTableCommand_L001B(uint16 d0, uint16 d6, uint32 &a3) {
+	uint16 slot = READ_BE_UINT16(&_mem[_labels.L004B + d0]);
+
+	uint8 saveD7 = _savedD7;
+	_savedD7 = read8(_labels.L004C + slot + 0) != 0 ? 1 : 0;
+
+	uint32 sequenceTranspose = _labels.L0048;
+	uint32 sequenceState = _labels.L0046;
+	uint32 sequenceStateIndex = _labels.L0045;
+	uint16 stateBase = READ_BE_UINT16(&_mem[sequenceStateIndex + d0]);
+
+	uint32 periodTable = _labels.L003C;
+	uint16 count = READ_BE_UINT16(&_mem[a3]);
+	a3 += 2;
+	WRITE_BE_UINT16(&_mem[sequenceState + stateBase + 0], 0);
+	uint16 countTimes2 = (uint16)(count + count);
+	WRITE_BE_UINT16(&_mem[sequenceState + stateBase + 2], countTimes2);
+	uint16 halfCount = (uint16)(countTimes2 >> 1);
+	WRITE_BE_UINT16(&_mem[sequenceState + stateBase + 4], 1);
+	WRITE_BE_UINT16(&_mem[sequenceState + stateBase + 6], READ_BE_UINT16(&_mem[a3]));
+	a3 += 2;
+
+	for (int16 loop = (int16)(halfCount - 2); loop >= 0; --loop) {
+		uint16 d4 = READ_BE_UINT16(&_mem[a3]);
+		a3 += 2;
+		d4 = addByteToLowWord(d4, read8(sequenceTranspose + d6));
+		if (_savedD7 != 0) {
+			WRITE_BE_UINT16(&_mem[sequenceState + stateBase + 10], d4);
+		} else {
+			uint16 idx = (uint16)(d4 + d4);
+			WRITE_BE_UINT16(&_mem[sequenceState + stateBase + 10], READ_BE_UINT16(&_mem[periodTable + idx]));
+		}
+		stateBase = (uint16)(stateBase + 2);
+	}
+
+	_savedD7 = saveD7;
+}
+
+
+void ElviraPrgDriver::updateActivePitchSlides_L0017() {
+	int16 d7 = 2;
+	uint16 d2 = 0;
+	uint32 a0 = _labels.L0042;
+	uint32 a1 = _labels.L0043;
+
+	while (true) {
+		int16 delta = (int16)READ_BE_UINT16(&_mem[a0]);
+		if (delta != 0) {
+			int16 countdown = (int16)READ_BE_UINT16(&_mem[a0 + 6]);
+			bool doStep = false;
+			if (countdown < 0) {
+				doStep = true;
+			} else {
+				countdown = (int16)(countdown - 1);
+				WRITE_BE_UINT16(&_mem[a0 + 6], (uint16)countdown);
+				if (countdown < 0)
+					doStep = true;
+			}
+
+			if (doStep) {
+				uint16 t = (uint16)((int16)READ_BE_UINT16(&_mem[a1]) + (int16)READ_BE_UINT16(&_mem[a0]));
+				WRITE_BE_UINT16(&_mem[a1], t);
+				psgWrite((uint8)d2, (uint8)(t & 0xFF));
+				psgWrite((uint8)(d2 + 1), (uint8)(t >> 8));
+
+				uint16 count = (uint16)(READ_BE_UINT16(&_mem[a0 + 2]) - 1);
+				WRITE_BE_UINT16(&_mem[a0 + 2], count);
+				if (count == 0) {
+					uint16 target = READ_BE_UINT16(&_mem[a0 + 4]);
+					psgWrite((uint8)d2, (uint8)(target & 0xFF));
+					psgWrite((uint8)(d2 + 1), (uint8)(target >> 8));
+					WRITE_BE_UINT16(&_mem[a1], target);
+					WRITE_BE_UINT16(&_mem[a0], 0);
+				}
+			}
+		}
+
+		a0 += 8;
+		a1 += 2;
+		d2 = (uint16)(d2 + 2);
+		if (--d7 < 0)
+			break;
+	}
+}
+
+void ElviraPrgDriver::updateSequencePitchLayers_L0020() {
+	uint32 voiceState = _labels.L004C;
+	uint32 sequenceState = _labels.L0046;
+	uint32 currentPeriods = _labels.L0043;
+	uint16 channelReg = 0;
+	int16 channelCount = 2;
+
+	while (true) {
+		uint16 basePeriod = READ_BE_UINT16(&_mem[currentPeriods]);
+		WRITE_BE_UINT16(&_mem[sequenceState + 8], basePeriod);
+		currentPeriods += 2;
+
+		uint16 seqLen = READ_BE_UINT16(&_mem[sequenceState + 2]);
+		if (seqLen != 0) {
+			uint16 countdown = (uint16)(READ_BE_UINT16(&_mem[sequenceState + 4]) - 1);
+			WRITE_BE_UINT16(&_mem[sequenceState + 4], countdown);
+			if (countdown == 0) {
+				uint16 reload = READ_BE_UINT16(&_mem[sequenceState + 6]);
+				WRITE_BE_UINT16(&_mem[sequenceState + 4], reload);
+
+				uint16 idx = READ_BE_UINT16(&_mem[sequenceState + 0]);
+				uint16 tablePeriod = READ_BE_UINT16(&_mem[sequenceState + 8 + idx]);
+				tablePeriod = addByteToLowWord(tablePeriod, read8(voiceState + 6));
+
+				if (read8(voiceState + 0) == 1) {
+					psgWrite(6, (uint8)(tablePeriod & 0xFF));
+				} else {
+					psgWrite((uint8)channelReg, (uint8)(tablePeriod & 0xFF));
+					psgWrite((uint8)(channelReg + 1), (uint8)((tablePeriod >> 8) & 0xFF));
+				}
+
+				uint16 nextIdx = (uint16)(idx + 2);
+				if (nextIdx >= seqLen)
+					nextIdx = 0;
+				WRITE_BE_UINT16(&_mem[sequenceState + 0], nextIdx);
+			}
+		}
+
+		sequenceState += 28;
+		channelReg += 2;
+		voiceState += 26;
+		if (--channelCount < 0)
+			break;
+	}
+}
+
+void ElviraPrgDriver::updatePerChannelVibrato_L0025() {
+	uint32 vibratoState = _labels.L004D;
+	uint32 currentPeriods = _labels.L0043;
+	uint32 sequenceState = _labels.L0046;
+	int16 channelCount = 2;
+	uint16 channelReg = 0;
+
+	while (true) {
+		bool skipOutput = false;
+
+		if (read8(vibratoState + 1) != 0) {
+			if ((int8)read8(vibratoState + 5) >= 0) {
+				uint8 v = (uint8)(read8(vibratoState + 5) - 1);
+				write8(vibratoState + 5, v);
+				if ((int8)v >= 0)
+					skipOutput = true;
+			}
+
+			if (!skipOutput) {
+				uint8 pos = read8(vibratoState + 0);
+				if (read8(vibratoState + 2) == 0) {
+					pos = (uint8)(pos + read8(vibratoState + 1));
+					write8(vibratoState + 0, pos);
+					if (pos == read8(vibratoState + 3))
+						write8(vibratoState + 2, (uint8)(read8(vibratoState + 2) ^ 1));
+				} else {
+					pos = (uint8)(pos - read8(vibratoState + 1));
+					write8(vibratoState + 0, pos);
+					if (pos == read8(vibratoState + 4))
+						write8(vibratoState + 2, (uint8)(read8(vibratoState + 2) ^ 1));
+				}
+
+				if (READ_BE_UINT16(&_mem[sequenceState + 2]) == 0) {
+					uint16 out = (uint16)((int16)READ_BE_UINT16(&_mem[currentPeriods]) + (int16)(int8)pos);
+					psgWrite((uint8)channelReg, (uint8)(out & 0xFF));
+					psgWrite((uint8)(channelReg + 1), (uint8)(out >> 8));
+				}
+			}
+		}
+
+		vibratoState += 26;
+		currentPeriods += 2;
+		sequenceState += 28;
+		channelReg = (uint16)(channelReg + 2);
+		if (--channelCount < 0)
+			break;
+	}
+}
+
+void ElviraPrgDriver::updatePendingMixerRestores_L002B() {
+	uint32 delayState = _labels.L004E;
+	int16 channelCount = 2;
+	uint16 mixerMask = 8;
+
+	while (true) {
+		if (read8(delayState) != 0) {
+			uint8 v = (uint8)(read8(delayState) - 1);
+			write8(delayState, v);
+			if (v == 0) {
+				uint16 mixer = (uint16)(READ_BE_UINT16(&_mem[_labels.L0049]) | mixerMask);
+				WRITE_BE_UINT16(&_mem[_labels.L0049], mixer);
+				psgWrite(7, (uint8)(mixer & 0xFF));
+			}
+		}
+		mixerMask = (uint16)(mixerMask + mixerMask);
+		delayState += 26;
+		if (--channelCount < 0)
+			break;
+	}
+}
+
+void ElviraPrgDriver::updateVolumeEnvelopeStages_L002E() {
+	uint32 voiceState = _labels.L004C;
+	int16 channelCount = 2;
+	uint8 volumeReg = 8;
+
+	while (true) {
+		uint8 stage = read8(voiceState + 14);
+		if (stage != 0) {
+			if (stage == 1) {
+				uint8 counter = (uint8)(read8(voiceState + 22) + 1);
+				if (counter == read8(voiceState + 15)) {
+					write8(voiceState + 1, (uint8)(read8(voiceState + 1) + read8(voiceState + 16)));
+					psgWrite(volumeReg, read8(voiceState + 1));
+					uint8 c = (uint8)(read8(voiceState + 17) - 1);
+					write8(voiceState + 17, c);
+					if (c == 0)
+						write8(voiceState + 14, (uint8)(read8(voiceState + 14) + 1));
+					counter = 0;
+				}
+				write8(voiceState + 22, counter);
+			} else if (stage == 2) {
+				uint8 c = (uint8)(read8(voiceState + 18) - 1);
+				write8(voiceState + 18, c);
+				if (c == 0)
+					write8(voiceState + 14, (uint8)(read8(voiceState + 14) + 1));
+			} else {
+				uint8 counter = (uint8)(read8(voiceState + 22) + 1);
+				if (counter == read8(voiceState + 19)) {
+					write8(voiceState + 1, (uint8)(read8(voiceState + 1) - read8(voiceState + 20)));
+					psgWrite(volumeReg, read8(voiceState + 1));
+					uint8 c = (uint8)(read8(voiceState + 21) - 1);
+					write8(voiceState + 21, c);
+					if (c == 0)
+						write8(voiceState + 14, 0);
+					counter = 0;
+				}
+				write8(voiceState + 22, counter);
+			}
+		}
+		voiceState += 26;
+		++volumeReg;
+		if (--channelCount < 0)
+			break;
+	}
+}
+
+void ElviraPrgDriver::updateAutomaticEnvelopeWrites_L0037() {
+	uint32 voiceState = _labels.L004C;
+	int16 channelCount = 2;
+	uint8 volumeReg = 8;
+
+	while (true) {
+		if (read8(voiceState + 5) != 0) {
+			psgWrite(0x0D, read8(voiceState + 5));
+			psgWrite(0x0B, read8(voiceState + 3));
+			psgWrite(0x0C, read8(voiceState + 2));
+			psgWrite(volumeReg, read8(voiceState + 1));
+		}
+		voiceState += 26;
+		++volumeReg;
+		if (--channelCount < 0)
+			break;
+	}
+}
+
+
+
+ElviraAtariSTPlayer::ElviraAtariSTPlayer(Common::SeekableReadStream *stream)
+	: _stream(stream) {
+	if (!_stream)
+		error("Atari ST YM player: null stream");
+
+	_stream->seek(0, SEEK_SET);
+	const uint32 sz = (uint32)_stream->size();
+	_data.resize(sz);
+	if (sz && _stream->read(&_data[0], sz) != sz)
+		error("Atari ST YM player: read failed");
+
+	_mode = kModeElvira2PKD;
+	_frameHz = kDefaultFrameHz;
+	_chip = YM2149::Config::create();
+	if (!_chip || !_chip->init())
+		error("Atari ST YM player: failed to initialise YM2149 core");
+
+	resetPlayer();
+	_chip->start(new Common::Functor0Mem<void, ElviraAtariSTPlayer>(this, &ElviraAtariSTPlayer::onTimer), _frameHz);
+}
+
+ElviraAtariSTPlayer::ElviraAtariSTPlayer(Common::SeekableReadStream *stream, uint16 elvira1Tune)
+	: _stream(stream), _elvira1Tune(elvira1Tune) {
+	if (!_stream)
+		error("Atari ST YM player: null stream");
+
+	_stream->seek(0, SEEK_SET);
+	const uint32 sz = (uint32)_stream->size();
+	_data.resize(sz);
+	if (sz && _stream->read(&_data[0], sz) != sz)
+		error("Atari ST YM player: read failed");
+
+	_mode = kModeElvira1PRG;
+	_frameHz = kElvira1PrgFrameHz;
+	_chip = YM2149::Config::create();
+	if (!_chip || !_chip->init())
+		error("Atari ST YM player: failed to initialise YM2149 core");
+
+	_elviraPrgDriver = new ElviraPrgDriver(this, _data);
+	if (!_elviraPrgDriver->isValid()) {
+		delete _elviraPrgDriver;
+		_elviraPrgDriver = nullptr;
+		_isValid = false;
+		return;
+	}
+
+	resetPlayer();
+	_chip->start(new Common::Functor0Mem<void, ElviraAtariSTPlayer>(this, &ElviraAtariSTPlayer::onTimer), _frameHz);
+}
+
+ElviraAtariSTPlayer::~ElviraAtariSTPlayer() {
+	if (_chip)
+		_chip->stop();
+	delete _elviraPrgDriver;
+	delete _chip;
+}
+
+void ElviraAtariSTPlayer::writeReg(int reg, uint8 data) {
+	if (_chip)
+		_chip->writeReg(reg, data);
+}
+
+void ElviraAtariSTPlayer::resetPlayer() {
+	_pos = 0;
+	_ended = false;
+	_framesLeftInWait = 0;
+	if (_chip)
+		_chip->reset();
+
+	if (_mode == kModeElvira1PRG) {
+		if (_elviraPrgDriver)
+			_elviraPrgDriver->init(_elvira1Tune);
+		else
+			_ended = true;
+		return;
+	}
+
+	parseUntilWait();
+}
+
+void ElviraAtariSTPlayer::parseUntilWait() {
+	if (_mode == kModeElvira1PRG) {
+		if (_elviraPrgDriver)
+			_elviraPrgDriver->vbl();
+		else
+			_ended = true;
+		return;
+	}
+
+	if (_ended)
+		return;
+
+	while (_pos + 1 < _data.size()) {
+		const uint8 r = _data[_pos++];
+		const uint8 v = _data[_pos++];
+
+		if (r == 0xFF) {
+			if (v == 0) {
+				resetPlayer();
+				return;
+			}
+			_framesLeftInWait = v;
+			return;
+		}
+
+		writeReg((int)r, v);
+	}
+
+	_ended = true;
+}
+
+void ElviraAtariSTPlayer::onTimer() {
+	if (_mode == kModeElvira2PKD && _framesLeftInWait) {
+		--_framesLeftInWait;
+		if (_framesLeftInWait)
+			return;
+	}
+
+	parseUntilWait();
+}
+
+} // namespace AGOS
diff --git a/engines/agos/drivers/elvira_atarist.h b/engines/agos/drivers/elvira_atarist.h
new file mode 100644
index 00000000000..a6f4d30bc04
--- /dev/null
+++ b/engines/agos/drivers/elvira_atarist.h
@@ -0,0 +1,77 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGOS_ELVIRA_ATARIST_H
+#define AGOS_ELVIRA_ATARIST_H
+
+#include "common/array.h"
+#include "common/ptr.h"
+#include "common/stream.h"
+
+namespace YM2149 {
+class YM2149;
+}
+
+namespace AGOS {
+
+class ElviraPrgDriver;
+
+class ElviraAtariSTPlayer {
+public:
+	ElviraAtariSTPlayer(Common::SeekableReadStream *stream);
+	ElviraAtariSTPlayer(Common::SeekableReadStream *stream, uint16 elvira1Tune);
+	~ElviraAtariSTPlayer();
+
+	bool isValid() const { return _isValid; }
+
+private:
+	friend class ElviraPrgDriver;
+
+	static const uint32 kDefaultFrameHz = 25;
+	static const uint32 kElvira1PrgFrameHz = 50;
+
+	Common::ScopedPtr<Common::SeekableReadStream> _stream;
+	Common::Array<uint8> _data;
+	size_t _pos = 0;
+	uint32 _frameHz = kDefaultFrameHz;
+	bool _ended = false;
+	uint32 _framesLeftInWait = 0;
+	uint16 _elvira1Tune = 1;
+	ElviraPrgDriver *_elviraPrgDriver = nullptr;
+	bool _isValid = true;
+	YM2149::YM2149 *_chip = nullptr;
+
+	enum Mode {
+		kModeElvira2PKD,
+		kModeElvira1PRG
+	};
+
+	Mode _mode = kModeElvira2PKD;
+
+	void resetPlayer();
+	void parseUntilWait();
+	void writeReg(int reg, uint8 data);
+	void onTimer();
+};
+
+} // namespace AGOS
+
+#endif
diff --git a/engines/agos/module.mk b/engines/agos/module.mk
index 2d11c9725f9..163898e4acc 100644
--- a/engines/agos/module.mk
+++ b/engines/agos/module.mk
@@ -7,6 +7,7 @@ MODULE_OBJS := \
 	drivers/accolade/driverfile.o \
 	drivers/accolade/pc98.o \
 	drivers/accolade/mt32.o \
+	drivers/elvira_atarist.o \
 	drivers/simon1/adlib.o \
 	agos.o \
 	charset.o \
diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp
index 45545d4daf9..fac14acc6e3 100644
--- a/engines/agos/res_snd.cpp
+++ b/engines/agos/res_snd.cpp
@@ -30,6 +30,7 @@
 #include "agos/midi.h"
 #include "agos/sound.h"
 #include "agos/vga.h"
+#include "drivers/elvira_atarist.h"
 
 #include "backends/audiocd/audiocd.h"
 
@@ -487,7 +488,69 @@ void AGOSEngine::playMusic(uint16 music, uint16 track) {
 	if (getPlatform() == Common::kPlatformAmiga) {
 		playModule(music);
 	} else if (getPlatform() == Common::kPlatformAtariST) {
-		// TODO: Add support for music formats used
+		if (getGameType() == GType_ELVIRA2) {
+			Common::File *file = new Common::File();
+			if (!file->open(Common::Path(Common::String::format("%dTUNE.PKD", music))))
+				error("playMusic: Can't load music from '%dTUNE.PKD'", music);
+
+			delete _elviraAtariSTPlayer;
+			_elviraAtariSTPlayer = nullptr;
+
+			_elviraAtariSTPlayer = new ElviraAtariSTPlayer(file);
+		} else if (getGameType() == GType_ELVIRA1) {
+			// Elvira 1 Atari ST scripts do not pass direct driver tune numbers.
+			// The original prg remaps the script music IDs to PRG subtunes:
+			//   1 -> 4
+			//   4 -> 2
+			//   7 -> 5
+			//   8 -> 7
+			//   9 -> 7
+			//  10 -> 6
+			//  14 -> 7
+			// 1 and 3 appear to be unused by the
+			// game's script-level music requests.
+			uint16 prgTune = 0;
+			switch (music) {
+			case 1:
+				prgTune = 4;
+				break;
+			case 4:
+				prgTune = 2;
+				break;
+			case 7:
+				prgTune = 5;
+				break;
+			case 8:
+			case 9:
+			case 14:
+				prgTune = 7;
+				break;
+			case 10:
+				prgTune = 6;
+				break;
+			default:
+				warning("playMusic: unsupported Elvira 1 Atari ST music id %d", music);
+				return;
+			}
+
+			Common::File *file = new Common::File();
+			if (!file->open(Common::Path("ELVIRA.PRG"))) {
+				warning("playMusic: Can't load Atari ST ELVIRA.PRG for music id %d", music);
+				delete file;
+				return;
+			}
+
+			delete _elviraAtariSTPlayer;
+			_elviraAtariSTPlayer = nullptr;
+
+			_elviraAtariSTPlayer = new ElviraAtariSTPlayer(file, prgTune);
+			if (!_elviraAtariSTPlayer->isValid()) {
+				warning("playMusic: Unsupported or unreadable Atari ST ELVIRA.PRG, skipping music id %d", music);
+				delete _elviraAtariSTPlayer;
+				_elviraAtariSTPlayer = nullptr;
+				return;
+			}
+		}
 	} else {
 		_midi->setLoop(true); // Must do this BEFORE loading music.
 
@@ -517,6 +580,9 @@ void AGOSEngine::stopMusic() {
 	_mixer->stopHandle(_modHandle);
 	_mixer->stopHandle(_digitalMusicHandle);
 
+	delete _elviraAtariSTPlayer;
+	_elviraAtariSTPlayer = nullptr;
+
 	debug(1, "AGOSEngine::stopMusic()");
 }
 




More information about the Scummvm-git-logs mailing list