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