1 // -*- Mode: C++; tab-width:2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 // vi:tw=80:et:ts=2:sts=2
3 //
4 // -----------------------------------------------------------------------
5 //
6 // This file is part of RLVM, a RealLive virtual machine clone.
7 //
8 // -----------------------------------------------------------------------
9 //
10 // Copyright (C) 2009 Elliot Glaysher
11 //
12 // This program is free software; you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation; either version 3 of the License, or
15 // (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
25 //
26 // -----------------------------------------------------------------------
27 //
28 // Parts of this file have been copied from koedec.cc in the xclannad
29 // distribution and they are:
30 //
31 // Copyright (c) 2004-2006 Kazunori "jagarl" Ueno
32 // All rights reserved.
33 //
34 // Redistribution and use in source and binary forms, with or without
35 // modification, are permitted provided that the following conditions
36 // are met:
37 // 1. Redistributions of source code must retain the above copyright
38 // notice, this list of conditions and the following disclaimer.
39 // 2. Redistributions in binary form must reproduce the above copyright
40 // notice, this list of conditions and the following disclaimer in the
41 // documentation and/or other materials provided with the distribution.
42 // 3. The name of the author may not be used to endorse or promote products
43 // derived from this software without specific prior written permission.
44 //
45 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
46 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
47 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
48 // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
49 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
50 // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
51 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
52 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
53 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
54 // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
55 //
56 // -----------------------------------------------------------------------
57
58 #include "systems/base/koepac_voice_archive.h"
59
60 #include <boost/filesystem/fstream.hpp>
61 #include <boost/filesystem/path.hpp>
62
63 #include <algorithm>
64 #include <cstring>
65 #include <fstream>
66 #include <sstream>
67 #include <vector>
68
69 #include "utilities/exception.h"
70 #include "xclannad/endian.hpp"
71
72 using std::ifstream;
73 using std::ostringstream;
74 namespace fs = boost::filesystem;
75
76 namespace {
77
78 // KOEPAC translation tables provided by Jagarl.
79
80 /* 8bit -> 16bit への変換テーブル。本来は signed short だが
81 ** とりあえず unsigned で扱っている
82 */
83
84 uint16_t koe_8bit_trans_tbl[256] = {
85 0x8000, 0x81ff, 0x83f9, 0x85ef, 0x87e1, 0x89cf, 0x8bb9, 0x8d9f, 0x8f81,
86 0x915f, 0x9339, 0x950f, 0x96e1, 0x98af, 0x9a79, 0x9c3f, 0x9e01, 0x9fbf,
87 0xa179, 0xa32f, 0xa4e1, 0xa68f, 0xa839, 0xa9df, 0xab81, 0xad1f, 0xaeb9,
88 0xb04f, 0xb1e1, 0xb36f, 0xb4f9, 0xb67f, 0xb801, 0xb97f, 0xbaf9, 0xbc6f,
89 0xbde1, 0xbf4f, 0xc0b9, 0xc21f, 0xc381, 0xc4df, 0xc639, 0xc78f, 0xc8e1,
90 0xca2f, 0xcb79, 0xccbf, 0xce01, 0xcf3f, 0xd079, 0xd1af, 0xd2e1, 0xd40f,
91 0xd539, 0xd65f, 0xd781, 0xd89f, 0xd9b9, 0xdacf, 0xdbe1, 0xdcef, 0xddf9,
92 0xdeff, 0xe001, 0xe0ff, 0xe1f9, 0xe2ef, 0xe3e1, 0xe4cf, 0xe5b9, 0xe69f,
93 0xe781, 0xe85f, 0xe939, 0xea0f, 0xeae1, 0xebaf, 0xec79, 0xed3f, 0xee01,
94 0xeebf, 0xef79, 0xf02f, 0xf0e1, 0xf18f, 0xf239, 0xf2df, 0xf381, 0xf41f,
95 0xf4b9, 0xf54f, 0xf5e1, 0xf66f, 0xf6f9, 0xf77f, 0xf801, 0xf87f, 0xf8f9,
96 0xf96f, 0xf9e1, 0xfa4f, 0xfab9, 0xfb1f, 0xfb81, 0xfbdf, 0xfc39, 0xfc8f,
97 0xfce1, 0xfd2f, 0xfd79, 0xfdbf, 0xfe01, 0xfe3f, 0xfe79, 0xfeaf, 0xfee1,
98 0xff0f, 0xff39, 0xff5f, 0xff81, 0xff9f, 0xffb9, 0xffcf, 0xffe1, 0xffef,
99 0xfff9, 0xffff, 0x0000, 0x0001, 0x0007, 0x0011, 0x001f, 0x0031, 0x0047,
100 0x0061, 0x007f, 0x00a1, 0x00c7, 0x00f1, 0x011f, 0x0151, 0x0187, 0x01c1,
101 0x01ff, 0x0241, 0x0287, 0x02d1, 0x031f, 0x0371, 0x03c7, 0x0421, 0x047f,
102 0x04e1, 0x0547, 0x05b1, 0x061f, 0x0691, 0x0707, 0x0781, 0x07ff, 0x0881,
103 0x0907, 0x0991, 0x0a1f, 0x0ab1, 0x0b47, 0x0be1, 0x0c7f, 0x0d21, 0x0dc7,
104 0x0e71, 0x0f1f, 0x0fd1, 0x1087, 0x1141, 0x11ff, 0x12c1, 0x1387, 0x1451,
105 0x151f, 0x15f1, 0x16c7, 0x17a1, 0x187f, 0x1961, 0x1a47, 0x1b31, 0x1c1f,
106 0x1d11, 0x1e07, 0x1f01, 0x1fff, 0x2101, 0x2207, 0x2311, 0x241f, 0x2531,
107 0x2647, 0x2761, 0x287f, 0x29a1, 0x2ac7, 0x2bf1, 0x2d1f, 0x2e51, 0x2f87,
108 0x30c1, 0x31ff, 0x3341, 0x3487, 0x35d1, 0x371f, 0x3871, 0x39c7, 0x3b21,
109 0x3c7f, 0x3de1, 0x3f47, 0x40b1, 0x421f, 0x4391, 0x4507, 0x4681, 0x47ff,
110 0x4981, 0x4b07, 0x4c91, 0x4e1f, 0x4fb1, 0x5147, 0x52e1, 0x547f, 0x5621,
111 0x57c7, 0x5971, 0x5b1f, 0x5cd1, 0x5e87, 0x6041, 0x61ff, 0x63c1, 0x6587,
112 0x6751, 0x691f, 0x6af1, 0x6cc7, 0x6ea1, 0x707f, 0x7261, 0x7447, 0x7631,
113 0x781f, 0x7a11, 0x7c07, 0x7fff};
114
115 /* ADPCM・・・じゃないらしい。ただのDPCMのナめたテーブル。
116 ** 自動生成すりゃいいんだけど256byteだったら
117 ** テーブルでも問題ないでしょ
118 */
119
120 uint8_t koe_ad_trans_tbl[256] = {
121 0x00, 0xff, 0x01, 0xfe, 0x02, 0xfd, 0x03, 0xfc, 0x04, 0xfb, 0x05, 0xfa,
122 0x06, 0xf9, 0x07, 0xf8, 0x08, 0xf7, 0x09, 0xf6, 0x0a, 0xf5, 0x0b, 0xf4,
123 0x0c, 0xf3, 0x0d, 0xf2, 0x0e, 0xf1, 0x0f, 0xf0, 0x10, 0xef, 0x11, 0xee,
124 0x12, 0xed, 0x13, 0xec, 0x14, 0xeb, 0x15, 0xea, 0x16, 0xe9, 0x17, 0xe8,
125 0x18, 0xe7, 0x19, 0xe6, 0x1a, 0xe5, 0x1b, 0xe4, 0x1c, 0xe3, 0x1d, 0xe2,
126 0x1e, 0xe1, 0x1f, 0xe0, 0x20, 0xdf, 0x21, 0xde, 0x22, 0xdd, 0x23, 0xdc,
127 0x24, 0xdb, 0x25, 0xda, 0x26, 0xd9, 0x27, 0xd8, 0x28, 0xd7, 0x29, 0xd6,
128 0x2a, 0xd5, 0x2b, 0xd4, 0x2c, 0xd3, 0x2d, 0xd2, 0x2e, 0xd1, 0x2f, 0xd0,
129 0x30, 0xcf, 0x31, 0xce, 0x32, 0xcd, 0x33, 0xcc, 0x34, 0xcb, 0x35, 0xca,
130 0x36, 0xc9, 0x37, 0xc8, 0x38, 0xc7, 0x39, 0xc6, 0x3a, 0xc5, 0x3b, 0xc4,
131 0x3c, 0xc3, 0x3d, 0xc2, 0x3e, 0xc1, 0x3f, 0xc0, 0x40, 0xbf, 0x41, 0xbe,
132 0x42, 0xbd, 0x43, 0xbc, 0x44, 0xbb, 0x45, 0xba, 0x46, 0xb9, 0x47, 0xb8,
133 0x48, 0xb7, 0x49, 0xb6, 0x4a, 0xb5, 0x4b, 0xb4, 0x4c, 0xb3, 0x4d, 0xb2,
134 0x4e, 0xb1, 0x4f, 0xb0, 0x50, 0xaf, 0x51, 0xae, 0x52, 0xad, 0x53, 0xac,
135 0x54, 0xab, 0x55, 0xaa, 0x56, 0xa9, 0x57, 0xa8, 0x58, 0xa7, 0x59, 0xa6,
136 0x5a, 0xa5, 0x5b, 0xa4, 0x5c, 0xa3, 0x5d, 0xa2, 0x5e, 0xa1, 0x5f, 0xa0,
137 0x60, 0x9f, 0x61, 0x9e, 0x62, 0x9d, 0x63, 0x9c, 0x64, 0x9b, 0x65, 0x9a,
138 0x66, 0x99, 0x67, 0x98, 0x68, 0x97, 0x69, 0x96, 0x6a, 0x95, 0x6b, 0x94,
139 0x6c, 0x93, 0x6d, 0x92, 0x6e, 0x91, 0x6f, 0x90, 0x70, 0x8f, 0x71, 0x8e,
140 0x72, 0x8d, 0x73, 0x8c, 0x74, 0x8b, 0x75, 0x8a, 0x76, 0x89, 0x77, 0x88,
141 0x78, 0x87, 0x79, 0x86, 0x7a, 0x85, 0x7b, 0x84, 0x7c, 0x83, 0x7d, 0x82,
142 0x7e, 0x81, 0x7f, 0x80};
143
144 } // namespace
145
146 // -----------------------------------------------------------------------
147 // KOEPACVoiceSample
148 // -----------------------------------------------------------------------
149 class KOEPACVoiceSample : public VoiceSample {
150 public:
KOEPACVoiceSample(fs::path file,int offset,int length,int rate)151 KOEPACVoiceSample(fs::path file, int offset, int length, int rate)
152 : stream_(std::fopen(file.native().c_str(), "rb")),
153 offset_(offset),
154 length_(length),
155 rate_(rate) {}
156
~KOEPACVoiceSample()157 virtual ~KOEPACVoiceSample() {
158 if (stream_)
159 fclose(stream_);
160 }
161
162 virtual char* Decode(int* size) override;
163
164 private:
165 FILE* stream_;
166 int offset_;
167 int length_;
168 int rate_;
169 };
170
Decode(int * dest_len)171 char* KOEPACVoiceSample::Decode(int* dest_len) {
172 // This function has been mildly adapted from decode_koe in xclannad. I have
173 // modified types so that it works on 64-bit systems and changed malloc()s to
174 // new[]s, as the consumer of decode() will delete [] the returned pointer.
175
176 // avg32 の声データ展開
177 std::unique_ptr<char[]> table(new char[length_ * 2]);
178 fseek(stream_, offset_, 0);
179 fread(table.get(), 2, length_, stream_);
180
181 int all_len = 0;
182 for (int i = 0; i < length_; i++)
183 all_len += read_little_endian_short(table.get() + i * 2);
184
185 // データ読み込み
186 uint8_t* src_orig = new uint8_t[all_len];
187 uint16_t* dest_orig = new uint16_t[length_ * 0x1000 + 0x2c];
188
189 if (src_orig == NULL || dest_orig == NULL)
190 return NULL;
191 uint8_t* src = src_orig;
192 fread(src, 1, all_len, stream_);
193 *dest_len = length_ * 0x400 * 4;
194 const char* header = MakeWavHeader(rate_, 2, 2, *dest_len);
195 memcpy(dest_orig, header, 0x2c);
196 uint16_t* dest = dest_orig + 0x2c;
197
198 // 展開
199 for (int i = 0; i < length_; i++) {
200 int slen = read_little_endian_short(table.get() + i * 2);
201 if (slen == 0) { // do nothing
202 memset(dest, 0, 0x1000);
203 dest += 0x800;
204 src += 0;
205 } else if (slen == 0x400) { // table 変換
206 for (int j = 0; j < 0x400; j++) {
207 write_little_endian_short((char*)(dest + 0), koe_8bit_trans_tbl[*src]);
208 write_little_endian_short((char*)(dest + 1), koe_8bit_trans_tbl[*src]);
209 dest += 2;
210 src++;
211 }
212 } else { // DPCM
213 uint8_t d = 0;
214 uint16_t o2;
215 for (int j = 0, k = 0; j < slen && k < 0x800; j++) {
216 uint8_t s = src[j];
217 if ((s + 1) & 0x0f) {
218 d -= koe_ad_trans_tbl[s & 0x0f];
219 } else {
220 uint8_t s2;
221 s >>= 4;
222 s &= 0x0f;
223 s2 = s;
224 s = src[++j];
225 s2 |= (s << 4) & 0xf0;
226 d -= koe_ad_trans_tbl[s2];
227 }
228 o2 = koe_8bit_trans_tbl[d];
229 write_little_endian_short((char*)(dest + k), o2);
230 write_little_endian_short((char*)(dest + k + 1), o2);
231 k += 2;
232 s >>= 4;
233 if ((s + 1) & 0x0f) {
234 d -= koe_ad_trans_tbl[s & 0x0f];
235 } else {
236 d -= koe_ad_trans_tbl[src[++j]];
237 }
238 o2 = koe_8bit_trans_tbl[d];
239 write_little_endian_short((char*)(dest + k), o2);
240 write_little_endian_short((char*)(dest + k + 1), o2);
241 k += 2;
242 }
243 dest += 0x800;
244 src += slen;
245 }
246 }
247 delete[] src_orig;
248
249 return (char*)dest_orig;
250 }
251
252 // -----------------------------------------------------------------------
253 // KOEPACVoiceArchive
254 // -----------------------------------------------------------------------
KOEPACVoiceArchive(fs::path file,int file_no)255 KOEPACVoiceArchive::KOEPACVoiceArchive(fs::path file, int file_no)
256 : VoiceArchive(file_no), file_(file) {
257 ReadTable(file);
258 }
259
260 // -----------------------------------------------------------------------
261
~KOEPACVoiceArchive()262 KOEPACVoiceArchive::~KOEPACVoiceArchive() {}
263
264 // -----------------------------------------------------------------------
265
FindSample(int sample_num)266 std::shared_ptr<VoiceSample> KOEPACVoiceArchive::FindSample(int sample_num) {
267 std::vector<Entry>::const_iterator it =
268 std::lower_bound(entries_.begin(), entries_.end(), sample_num);
269 if (it != entries_.end()) {
270 return std::shared_ptr<VoiceSample>(
271 new KOEPACVoiceSample(file_, it->offset, it->length, rate_));
272 }
273
274 throw rlvm::Exception("Couldn't find sample in KOEPACVoiceArchive");
275 }
276
277 // -----------------------------------------------------------------------
278
ReadTable(boost::filesystem::path file)279 void KOEPACVoiceArchive::ReadTable(boost::filesystem::path file) {
280 fs::ifstream ifs(file, ifstream::in | ifstream::binary);
281 if (!ifs) {
282 std::ostringstream oss;
283 oss << "Could not open file \"" << file << "\".";
284 throw rlvm::Exception(oss.str());
285 }
286
287 // Copied from koedec.cc
288 char head[0x20];
289 ifs.read(head, 0x20);
290 if (strncmp(head, "KOEPAC", 7) != 0) {
291 std::ostringstream oss;
292 oss << file << " does not appear to be in KOEPAC format";
293 throw rlvm::Exception(oss.str());
294 }
295
296 int table_len = read_little_endian_int(head + 0x10);
297 entries_.reserve(table_len);
298
299 rate_ = read_little_endian_int(head + 0x18);
300 if (rate_ == 0) {
301 rate_ = 22050;
302 }
303
304 char* buf = new char[table_len * 8];
305 ifs.read(buf, table_len * 8);
306 for (int i = 0; i < table_len; i++) {
307 int koe_num = read_little_endian_short(buf + i * 8);
308 int length = read_little_endian_short(buf + i * 8 + 2);
309 int offset = read_little_endian_int(buf + i * 8 + 4);
310 entries_.emplace_back(koe_num, length, offset);
311 }
312 sort(entries_.begin(), entries_.end());
313
314 delete[] buf;
315 }
316