Add a dialog in scenario editor to import/export custom sounds in WAV format

This commit is contained in:
2015-06-17 01:03:17 -04:00
parent e055b97c9f
commit 580f70f49a
15 changed files with 264 additions and 18 deletions

View File

@@ -0,0 +1,85 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<?xml-stylesheet href="dialog.xsl" type="text/xsl"?>
<dialog skin='light' defbtn='okay'>
<pict type='dlog' num='16' top='6' left='6'/>
<text size='large' top='18' left='50' width='250' height='17'>Edit Custom Sounds:</text>
<text top='48' left='10' width='50' height='16'>Sound #</text>
<text top='48' left='65' width='200' height='16'>Name</text>
<text name='num0' top='68' left='10' width='50' height='16'/>
<text name='num1' top='98' left='10' width='50' height='16'/>
<text name='num2' top='128' left='10' width='50' height='16'/>
<text name='num3' top='158' left='10' width='50' height='16'/>
<text name='num4' top='188' left='10' width='50' height='16'/>
<text name='num5' top='218' left='10' width='50' height='16'/>
<text name='num6' top='248' left='10' width='50' height='16'/>
<text name='num7' top='278' left='10' width='50' height='16'/>
<text name='num8' top='308' left='10' width='50' height='16'/>
<text name='num9' top='338' left='10' width='50' height='16'/>
<field name='name0' top='67' left='65' width='200' height='16'/>
<field name='name1' top='97' left='65' width='200' height='16'/>
<field name='name2' top='127' left='65' width='200' height='16'/>
<field name='name3' top='157' left='65' width='200' height='16'/>
<field name='name4' top='187' left='65' width='200' height='16'/>
<field name='name5' top='217' left='65' width='200' height='16'/>
<field name='name6' top='247' left='65' width='200' height='16'/>
<field name='name7' top='277' left='65' width='200' height='16'/>
<field name='name8' top='307' left='65' width='200' height='16'/>
<field name='name9' top='337' left='65' width='200' height='16'/>
<button name='open0' type='regular' top='66' left='275'>Import</button>
<button name='open1' type='regular' top='96' left='275'>Import</button>
<button name='open2' type='regular' top='126' left='275'>Import</button>
<button name='open3' type='regular' top='156' left='275'>Import</button>
<button name='open4' type='regular' top='186' left='275'>Import</button>
<button name='open5' type='regular' top='216' left='275'>Import</button>
<button name='open6' type='regular' top='246' left='275'>Import</button>
<button name='open7' type='regular' top='276' left='275'>Import</button>
<button name='open8' type='regular' top='306' left='275'>Import</button>
<button name='open9' type='regular' top='336' left='275'>Import</button>
<button name='save0' type='regular' top='66' left='340'>Export</button>
<button name='save1' type='regular' top='96' left='340'>Export</button>
<button name='save2' type='regular' top='126' left='340'>Export</button>
<button name='save3' type='regular' top='156' left='340'>Export</button>
<button name='save4' type='regular' top='186' left='340'>Export</button>
<button name='save5' type='regular' top='216' left='340'>Export</button>
<button name='save6' type='regular' top='246' left='340'>Export</button>
<button name='save7' type='regular' top='276' left='340'>Export</button>
<button name='save8' type='regular' top='306' left='340'>Export</button>
<button name='save9' type='regular' top='336' left='340'>Export</button>
<button name='play0' type='regular' top='66' left='405'>Play</button>
<button name='play1' type='regular' top='96' left='405'>Play</button>
<button name='play2' type='regular' top='126' left='405'>Play</button>
<button name='play3' type='regular' top='156' left='405'>Play</button>
<button name='play4' type='regular' top='186' left='405'>Play</button>
<button name='play5' type='regular' top='216' left='405'>Play</button>
<button name='play6' type='regular' top='246' left='405'>Play</button>
<button name='play7' type='regular' top='276' left='405'>Play</button>
<button name='play8' type='regular' top='306' left='405'>Play</button>
<button name='play9' type='regular' top='336' left='405'>Play</button>
<button name='del0' type='regular' top='66' left='470'>Delete</button>
<button name='del1' type='regular' top='96' left='470'>Delete</button>
<button name='del2' type='regular' top='126' left='470'>Delete</button>
<button name='del3' type='regular' top='156' left='470'>Delete</button>
<button name='del4' type='regular' top='186' left='470'>Delete</button>
<button name='del5' type='regular' top='216' left='470'>Delete</button>
<button name='del6' type='regular' top='246' left='470'>Delete</button>
<button name='del7' type='regular' top='276' left='470'>Delete</button>
<button name='del8' type='regular' top='306' left='470'>Delete</button>
<button name='del9' type='regular' top='336' left='470'>Delete</button>
<text top='364' left='20' width='500' height='34'>
The Cancel button only discards changes made to the sound names.
If you import a sound, that change is immediately saved, so Cancel
will not discard it.
</text>
<button name='left' type='left' def-key='left' top='386' left='20'/>
<button name='right' type='right' def-key='right' top='386' left='85'/>
<button name='cancel' type='regular' def-key='esc' top='386' left='405'>Cancel</button>
<button name='okay' type='regular' top='386' left='470'>OK</button>
</dialog>

View File

@@ -373,7 +373,7 @@
<reference key="NSOnImage" ref="229763992"/>
<reference key="NSMixedImage" ref="909111550"/>
</object>
<object class="NSMenuItem" id="121323895">
<object class="NSMenuItem" id="1072664025">
<reference key="NSMenu" ref="399390342"/>
<string key="NSTitle">Edit Custom Graphic Sheets</string>
<string key="NSKeyEquiv"/>
@@ -389,6 +389,14 @@
<reference key="NSOnImage" ref="229763992"/>
<reference key="NSMixedImage" ref="909111550"/>
</object>
<object class="NSMenuItem" id="368560496">
<reference key="NSMenu" ref="399390342"/>
<string key="NSTitle">Edit Custom Sounds</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="229763992"/>
<reference key="NSMixedImage" ref="909111550"/>
</object>
<object class="NSMenuItem" id="1007446984">
<reference key="NSMenu" ref="399390342"/>
<bool key="NSIsDisabled">YES</bool>
@@ -1247,7 +1255,8 @@
<reference ref="949025402"/>
<reference ref="72958416"/>
<reference ref="963208893"/>
<reference ref="121323895"/>
<reference ref="1072664025"/>
<reference ref="368560496"/>
</array>
<reference key="parent" ref="741259600"/>
</object>
@@ -1603,7 +1612,12 @@
</object>
<object class="IBObjectRecord">
<int key="objectID">888</int>
<reference key="object" ref="121323895"/>
<reference key="object" ref="1072664025"/>
<reference key="parent" ref="399390342"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">889</int>
<reference key="object" ref="368560496"/>
<reference key="parent" ref="399390342"/>
</object>
</array>
@@ -1713,12 +1727,13 @@
<string key="886.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="887.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="888.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="889.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
<nil key="activeLocalization"/>
<dictionary class="NSMutableDictionary" key="localizations"/>
<nil key="sourceID"/>
<int key="maxID">888</int>
<int key="maxID">889</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes"/>
<int key="IBDocument.localizationMode">0</int>

View File

@@ -351,6 +351,15 @@
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="sound" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="id" type="xs:integer" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>

View File

@@ -92,6 +92,7 @@ BEGIN
MENUITEM "Set Starting &Location", IDM_SCEN_START
MENUITEM "Edit Custom Graphic Sheets", IDM_SCEN_CUSTOM_SHEETS
MENUITEM "Classify Custom &Graphics", IDM_SCEN_CUSTOM_PICS
MENUITEM "Edit Custom Sounds", IDM_SCEN_CUSTOM_SNDS
MENUITEM SEPARATOR
MENUITEM "Advanced:", IDM_SCEN_NEW_TOWN, GRAYED
MENUITEM " Edit Special &Nodes", IDM_SCEN_ADV_SPECIALS

View File

@@ -67,6 +67,7 @@
#define IDM_EDIT_SELECT 162
#define IDM_SCEN_CUSTOM_PICS 163
#define IDM_SCEN_CUSTOM_SHEETS 164
#define IDM_SCEN_CUSTOM_SNDS 165
// Next default values for new objects
//
@@ -75,6 +76,6 @@
#define _APS_NEXT_RESOURCE_VALUE 103
#define _APS_NEXT_COMMAND_VALUE 40014
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 165
#define _APS_NEXT_SYMED_VALUE 166
#endif
#endif

View File

@@ -93,6 +93,7 @@ public:
std::string intro_strs[6];
std::vector<std::string> journal_strs;
std::vector<std::string> spec_strs;
std::vector<std::string> snd_names;
bool adjust_diff;
bool is_legacy;
fs::path scen_file; // transient

View File

@@ -3407,3 +3407,120 @@ void edit_custom_sheets() {
shut_down_menus(editing_town ? 2 : 1);
else shut_down_menus(3);
}
extern fs::path tempDir;
static bool edit_custom_sound_action(cDialog& me, std::string action, int curPage, int& max_snd) {
size_t a_len = action.length();
int which_snd = (curPage + 1) * 100 + (action[a_len-1] - '0');
action.erase(action.end() - 1);
fs::path sndpath = tempDir/"scenario/sounds";
std::string sndbasenm = "SND" + std::to_string(which_snd);
fs::path sndfile = sndpath/(sndbasenm + ".wav");
if(action != "open" && !fs::exists(sndfile)) {
beep();
return true;
}
if(action == "play") {
play_sound(-which_snd);
} else if(action == "del") {
// TODO: Implement this action
} else if(action == "open") {
fs::path fpath = nav_get_rsrc({"wav"});
if(fpath.empty()) return true;
sf::SoundBuffer snd;
if(!snd.loadFromFile(fpath.string().c_str())) {
beep();
return true;
}
fs::copy_file(fpath, sndfile, fs::copy_option::overwrite_if_exists);
ResMgr::free<SoundRsrc>(which_snd);
if(which_snd > max_snd)
max_snd = which_snd;
if(max_snd % 10 == 9) {
me["left"].show();
me["right"].show();
}
} else if(action == "save") {
fs::path fpath = nav_put_rsrc({"wav"});
if(fpath.empty()) return true;
fs::copy_file(sndfile, fpath, fs::copy_option::overwrite_if_exists);
}
return true;
}
static void fill_custom_sounds_page(cDialog& me, const std::vector<std::string>& snd_names, int& curPage, int& max_snd, bool firstTime) {
for(int i = 0; i < 10; i++) {
int which_snd = (curPage + 1) * 100 + i;
std::string id = std::to_string(i);
if(firstTime) {
using namespace std::placeholders;
std::vector<std::string> buttons = {
"play" + id, "del" + id,
"open" + id, "save" + id,
};
me.attachClickHandlers(std::bind(edit_custom_sound_action, _1, _2, std::ref(curPage), std::ref(max_snd)), buttons);
}
me["num" + id].setTextToNum(which_snd);
if(which_snd - 100 < snd_names.size())
me["name" + id].setText(snd_names[which_snd - 100]);
}
}
static void get_sound_names_from_dlg(cDialog& me, std::vector<std::string>& snd_names, int curPage) {
for(int i = 9; i >= 0; i--) {
std::string id = "name" + std::to_string(i);
size_t index = curPage * 100 + i;
std::string name = me[id].getText();
if(!name.empty()) {
if(snd_names.size() <= index)
snd_names.resize(index + 1);
snd_names[index] = name;
} else if(index < snd_names.size())
snd_names[index].clear();
}
}
void edit_custom_sounds() {
cDialog snd_dlg("edit-sounds");
snd_dlg["cancel"].attachClickHandler(std::bind(&cDialog::toast, &snd_dlg, false));
snd_dlg["okay"].attachClickHandler(std::bind(&cDialog::toast, &snd_dlg, true));
int max_snd = 99;
fs::path snd_dir = tempDir/"scenario/sounds";
if(!fs::exists(snd_dir)) fs::create_directories(snd_dir);
for(fs::directory_iterator iter(snd_dir); iter != fs::directory_iterator(); iter++) {
std::string fname = iter->path().filename().string().c_str();
int dot = fname.find_last_of('.');
if(fname.substr(0,3) == "snd" && fname.substr(dot) == ".wav" && std::all_of(fname.begin()+3, fname.begin()+dot, isdigit))
max_snd = max(max_snd, boost::lexical_cast<int>(fname.substr(3,dot-3)));
}
int curPage = 0;
fill_custom_sounds_page(snd_dlg, scenario.snd_names, curPage, max_snd, true);
auto snd_names = scenario.snd_names;
if(max_snd < 110) {
snd_dlg["left"].hide();
snd_dlg["right"].hide();
}
snd_dlg.attachClickHandlers([&curPage,&max_snd,&snd_names](cDialog& me, std::string dir, eKeyMod) -> bool {
get_sound_names_from_dlg(me, snd_names, curPage);
if(dir == "left") {
if(curPage == 0)
curPage = max_snd / 10 - 10;
else curPage--;
} else if(dir == "right") {
curPage++;
if(curPage > max_snd / 10 - 10)
curPage = 0;
} else return true;
fill_custom_sounds_page(me, snd_names, curPage, max_snd, false);
return true;
}, {"left", "right"});
snd_dlg.run();
if(snd_dlg.accepted()) {
get_sound_names_from_dlg(snd_dlg, snd_names, curPage);
snd_names.swap(scenario.snd_names);
}
}

View File

@@ -1,6 +1,7 @@
class cDialog;
void edit_custom_sounds();
void edit_custom_sheets();
void edit_custom_pics_types();
bool edit_ter_type(ter_num_t which_ter);

View File

@@ -337,6 +337,13 @@ static void writeScenarioToXml(ticpp::Printer&& data) {
data.CloseElement("storage");
}
}
for(int i = 0; i < scenario.snd_names.size(); i++) {
if(scenario.snd_names[i].empty()) continue;
data.OpenElement("sound");
data.PushAttribute("id", i);
data.PushText(scenario.snd_names[i]);
data.CloseElement("sound");
}
data.CloseElement("editor");
data.CloseElement("scenario");
}
@@ -1050,7 +1057,7 @@ void save_scenario(fs::path toFile) {
std::string fname = dir_iter->path().filename().string();
if(fname.substr(0,3) == "SND") {
size_t dot = fname.find_last_of('.');
if(fname.substr(dot) == ".WAV" && std::all_of(fname.begin() + 3, fname.begin() + dot, isdigit)) {
if(fname.substr(dot) == ".wav" && std::all_of(fname.begin() + 3, fname.begin() + dot, isdigit)) {
// Looks like a valid sound!
std::ostream& pic_out = scen_file.newFile("scenario/sounds/" + fname);
std::ifstream fin(dir_iter->path().string().c_str(), std::ios::binary);

View File

@@ -761,9 +761,10 @@ pic_num_t choose_status_effect(short cur, bool party, cDialog* parent) {
}
snd_num_t choose_sound(short cur, cDialog* parent, std::string title) {
// TODO: Somehow find a way to include custom sounds in this list
if(cur < 0) cur = 0;
cStringChoice snd_dlg(*ResMgr::get<StringRsrc>("sound-names"), title, parent);
StringRsrc snd_names = *ResMgr::get<StringRsrc>("sound-names");
std::copy(scenario.snd_names.begin(), scenario.snd_names.end(), std::back_inserter(snd_names));
cStringChoice snd_dlg(snd_names, title, parent);
snd_dlg.attachSelectHandler([](cStringChoice&, int n) {
play_sound(-n);
});

View File

@@ -304,6 +304,10 @@ void handle_menu_choice(eMenu item_hit) {
edit_custom_pics_types();
change_made = true;
break;
case eMenu::SCEN_SNDS:
edit_custom_sounds();
change_made = true;
break;
case eMenu::SCEN_SPECIALS:
right_sbar->setPosition(0);
start_special_editing(0,0);

View File

@@ -23,7 +23,7 @@ enum class eMenu {
SCEN_SAVE_ITEM_RECTS, SCEN_HORSES, SCEN_BOATS,
TOWN_VARYING, SCEN_TIMERS, SCEN_ITEM_SHORTCUTS, TOWN_DELETE,
SCEN_DATA_DUMP, SCEN_TEXT_DUMP,
SCEN_PICS, SCEN_SHEETS,
SCEN_PICS, SCEN_SHEETS, SCEN_SNDS,
// Town menu
TOWN_DETAILS, TOWN_WANDERING, TOWN_BOUNDARIES, TOWN_AREAS,
TOWN_ITEMS_RANDOM, TOWN_ITEMS_NOT_PROPERTY, TOWN_ITEMS_CLEAR,

View File

@@ -52,7 +52,7 @@ void init_menubar() {
eMenu::EDIT_CUT, eMenu::EDIT_COPY, eMenu::EDIT_PASTE, eMenu::EDIT_DELETE, eMenu::EDIT_SELECT_ALL,
};
static const eMenu scen_choices[] = {
eMenu::TOWN_CREATE, eMenu::NONE, eMenu::SCEN_DETAILS, eMenu::SCEN_INTRO, eMenu::TOWN_START, eMenu::SCEN_SHEETS, eMenu::SCEN_PICS, eMenu::NONE, eMenu::NONE,
eMenu::TOWN_CREATE, eMenu::NONE, eMenu::SCEN_DETAILS, eMenu::SCEN_INTRO, eMenu::TOWN_START, eMenu::SCEN_SHEETS, eMenu::SCEN_PICS, eMenu::SCEN_SNDS, eMenu::NONE, eMenu::NONE,
eMenu::SCEN_SPECIALS, eMenu::SCEN_TEXT, eMenu::SCEN_JOURNALS, eMenu::TOWN_IMPORT, eMenu::SCEN_SAVE_ITEM_RECTS,
eMenu::SCEN_HORSES, eMenu::SCEN_BOATS, eMenu::TOWN_VARYING, eMenu::SCEN_TIMERS, eMenu::SCEN_ITEM_SHORTCUTS,
eMenu::TOWN_DELETE, eMenu::SCEN_DATA_DUMP, eMenu::SCEN_TEXT_DUMP,

View File

@@ -80,7 +80,7 @@ void init_menubar() {
eMenu::EDIT_CUT, eMenu::EDIT_COPY, eMenu::EDIT_PASTE, eMenu::EDIT_DELETE, eMenu::EDIT_SELECT_ALL,
};
static const eMenu scen_choices[] = {
eMenu::TOWN_CREATE, eMenu::NONE, eMenu::SCEN_DETAILS, eMenu::SCEN_INTRO, eMenu::TOWN_START, eMenu::SCEN_SHEETS, eMenu::SCEN_PICS, eMenu::NONE, eMenu::NONE,
eMenu::TOWN_CREATE, eMenu::NONE, eMenu::SCEN_DETAILS, eMenu::SCEN_INTRO, eMenu::TOWN_START, eMenu::SCEN_SHEETS, eMenu::SCEN_PICS, eMenu::SCEN_SNDS, eMenu::NONE, eMenu::NONE,
eMenu::SCEN_SPECIALS, eMenu::SCEN_TEXT, eMenu::SCEN_JOURNALS, eMenu::TOWN_IMPORT, eMenu::SCEN_SAVE_ITEM_RECTS,
eMenu::SCEN_HORSES, eMenu::SCEN_BOATS, eMenu::TOWN_VARYING, eMenu::SCEN_TIMERS, eMenu::SCEN_ITEM_SHORTCUTS,
eMenu::TOWN_DELETE, eMenu::SCEN_DATA_DUMP, eMenu::SCEN_TEXT_DUMP,

View File

@@ -757,7 +757,7 @@ static void readScenarioFromXml(ticpp::Document&& data, cScenario& scenario) {
}
} else if(type == "editor") {
Iterator<Element> edit;
int num_storage = 0, num_pics = 0;
int num_storage = 0, num_pics = 0, sndnum = 0;
for(edit = edit.begin(elem.Get()); edit != edit.end(); edit++) {
edit->GetValue(&type);
if(type == "default-ground") {
@@ -766,6 +766,11 @@ static void readScenarioFromXml(ticpp::Document&& data, cScenario& scenario) {
scenario.last_out_edited = readLocFromXml(*edit);
} else if(type == "last-town") {
edit->GetText(&scenario.last_town_edited);
} else if(type == "sound") {
edit->GetAttribute("id", &sndnum);
if(sndnum >= scenario.snd_names.size())
scenario.snd_names.resize(sndnum + 1);
edit->GetText(&scenario.snd_names[sndnum], false);
} else if(type == "graphics") {
static const std::set<int> valid_pictypes = {1,2,3,4,5,7,10,11,12,13,15,16,23,43,63};
if(num_pics > 0)
@@ -1875,6 +1880,7 @@ bool load_scenario_v2(fs::path file_to_load, cScenario& scenario) {
// First figure out where they are in the filesystem. The implementation of this depends on whether the scenario is packed.
int num_graphic_sheets = 0;
if(is_packed) {
fs::remove_all(tempDir/"scenario");
int i = 0;
std::string fname;
while(fname = "scenario/graphics/sheet" + std::to_string(i) + ".png", pack.hasFile(fname)) {
@@ -1886,11 +1892,10 @@ bool load_scenario_v2(fs::path file_to_load, cScenario& scenario) {
fout.close();
i++;
}
if(i > 0)
ResMgr::pushPath<ImageRsrc>(tempDir/"scenario"/"graphics");
ResMgr::pushPath<ImageRsrc>(tempDir/"scenario"/"graphics");
num_graphic_sheets = i;
i = 0;
while(fname = "scenario/sounds/SND" + std::to_string(i) + ".WAV", pack.hasFile(fname)) {
i = 100;
while(fname = "scenario/sounds/SND" + std::to_string(i) + ".wav", pack.hasFile(fname)) {
fs::path path = tempDir/fname;
fs::create_directories(path.parent_path());
std::istream& snd = pack.getFile(fname);
@@ -1899,8 +1904,7 @@ bool load_scenario_v2(fs::path file_to_load, cScenario& scenario) {
fout.close();
i++;
}
if(i > 0)
ResMgr::pushPath<SoundRsrc>(tempDir/"scenario"/"sounds");
ResMgr::pushPath<SoundRsrc>(tempDir/"scenario"/"sounds");
} else {
if(fs::is_directory(file_to_load/"graphics"))
ResMgr::pushPath<ImageRsrc>(file_to_load/"graphics");