Merge branch 'tildearrow:master' into SID3
This commit is contained in:
commit
bc352728b5
18 changed files with 1477 additions and 73 deletions
341
src/gui/gui.cpp
341
src/gui/gui.cpp
|
|
@ -3828,8 +3828,9 @@ bool FurnaceGUI::loop() {
|
|||
}
|
||||
int sampleCountBefore=e->song.sampleLen;
|
||||
std::vector<DivInstrument*> instruments=e->instrumentFromFile(ev.drop.file,true,settings.readInsNames);
|
||||
std::vector<DivSample*> samples = e->sampleFromFile(ev.drop.file);
|
||||
DivWavetable* droppedWave=NULL;
|
||||
DivSample* droppedSample=NULL;
|
||||
//DivSample* droppedSample=NULL;
|
||||
if (!instruments.empty()) {
|
||||
if (e->song.sampleLen!=sampleCountBefore) {
|
||||
e->renderSamplesP();
|
||||
|
|
@ -3854,10 +3855,24 @@ bool FurnaceGUI::loop() {
|
|||
}
|
||||
nextWindow=GUI_WINDOW_WAVE_LIST;
|
||||
MARK_MODIFIED;
|
||||
} else if ((droppedSample=e->sampleFromFile(ev.drop.file))!=NULL) {
|
||||
}
|
||||
else if (!samples.empty())
|
||||
{
|
||||
if (e->song.sampleLen!=sampleCountBefore) {
|
||||
//e->renderSamplesP();
|
||||
}
|
||||
if (!e->getWarnings().empty())
|
||||
{
|
||||
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
|
||||
}
|
||||
int sampleCount=-1;
|
||||
sampleCount=e->addSamplePtr(droppedSample);
|
||||
if (sampleCount>=0 && settings.selectAssetOnLoad) {
|
||||
for (DivSample* s: samples)
|
||||
{
|
||||
sampleCount=e->addSamplePtr(s);
|
||||
}
|
||||
//sampleCount=e->addSamplePtr(droppedSample);
|
||||
if (sampleCount>=0 && settings.selectAssetOnLoad)
|
||||
{
|
||||
curSample=sampleCount;
|
||||
updateSampleTex=true;
|
||||
}
|
||||
|
|
@ -5319,24 +5334,43 @@ bool FurnaceGUI::loop() {
|
|||
String errs=_("there were some errors while loading samples:\n");
|
||||
bool warn=false;
|
||||
for (String i: fileDialog->getFileName()) {
|
||||
DivSample* s=e->sampleFromFile(i.c_str());
|
||||
if (s==NULL) {
|
||||
std::vector<DivSample*> samples=e->sampleFromFile(i.c_str());
|
||||
if (samples.empty()) {
|
||||
if (fileDialog->getFileName().size()>1) {
|
||||
warn=true;
|
||||
errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError());
|
||||
} else {;
|
||||
showError(e->getLastError());
|
||||
}
|
||||
} else {
|
||||
if (e->addSamplePtr(s)==-1) {
|
||||
if (fileDialog->getFileName().size()>1) {
|
||||
warn=true;
|
||||
errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError());
|
||||
} else {
|
||||
showError(e->getLastError());
|
||||
}
|
||||
else
|
||||
{
|
||||
if((int)samples.size() == 1)
|
||||
{
|
||||
if (e->addSamplePtr(samples[0]) == -1)
|
||||
{
|
||||
if (fileDialog->getFileName().size()>1)
|
||||
{
|
||||
warn=true;
|
||||
errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError());
|
||||
}
|
||||
else
|
||||
{
|
||||
showError(e->getLastError());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MARK_MODIFIED;
|
||||
}
|
||||
} else {
|
||||
MARK_MODIFIED;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (DivSample* s: samples) { //ask which samples to load!
|
||||
pendingSamples.push_back(std::make_pair(s,false));
|
||||
}
|
||||
displayPendingSamples=true;
|
||||
replacePendingSample = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5345,24 +5379,44 @@ bool FurnaceGUI::loop() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case GUI_FILE_SAMPLE_OPEN_REPLACE: {
|
||||
DivSample* s=e->sampleFromFile(copyOfName.c_str());
|
||||
if (s==NULL) {
|
||||
case GUI_FILE_SAMPLE_OPEN_REPLACE:
|
||||
{
|
||||
std::vector<DivSample*> samples=e->sampleFromFile(copyOfName.c_str());
|
||||
if (samples.empty())
|
||||
{
|
||||
showError(e->getLastError());
|
||||
} else {
|
||||
if (curSample>=0 && curSample<(int)e->song.sample.size()) {
|
||||
e->lockEngine([this,s]() {
|
||||
// if it crashes here please tell me...
|
||||
DivSample* oldSample=e->song.sample[curSample];
|
||||
e->song.sample[curSample]=s;
|
||||
delete oldSample;
|
||||
e->renderSamples();
|
||||
MARK_MODIFIED;
|
||||
});
|
||||
updateSampleTex=true;
|
||||
} else {
|
||||
showError(_("...but you haven't selected a sample!"));
|
||||
delete s;
|
||||
}
|
||||
else
|
||||
{
|
||||
if((int)samples.size() == 1)
|
||||
{
|
||||
if (curSample>=0 && curSample<(int)e->song.sample.size())
|
||||
{
|
||||
DivSample* s = samples[0];
|
||||
e->lockEngine([this, s]()
|
||||
{
|
||||
// if it crashes here please tell me...
|
||||
DivSample* oldSample=e->song.sample[curSample];
|
||||
e->song.sample[curSample]= s;
|
||||
delete oldSample;
|
||||
e->renderSamples();
|
||||
MARK_MODIFIED;
|
||||
});
|
||||
updateSampleTex=true;
|
||||
}
|
||||
else
|
||||
{
|
||||
showError(_("...but you haven't selected a sample!"));
|
||||
delete samples[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (DivSample* s: samples) { //ask which samples to load!
|
||||
pendingSamples.push_back(std::make_pair(s,false));
|
||||
}
|
||||
displayPendingSamples=true;
|
||||
replacePendingSample = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -5741,6 +5795,11 @@ bool FurnaceGUI::loop() {
|
|||
ImGui::OpenPopup(_("Select Instrument"));
|
||||
}
|
||||
|
||||
if (displayPendingSamples) {
|
||||
displayPendingSamples=false;
|
||||
ImGui::OpenPopup(_("Select Sample"));
|
||||
}
|
||||
|
||||
if (displayPendingRawSample) {
|
||||
displayPendingRawSample=false;
|
||||
ImGui::OpenPopup(_("Import Raw Sample"));
|
||||
|
|
@ -6569,6 +6628,191 @@ bool FurnaceGUI::loop() {
|
|||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
// TODO: fix style
|
||||
centerNextWindow(_("Select Sample"),canvasW,canvasH);
|
||||
if (ImGui::BeginPopupModal(_("Select Sample"),NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
bool quitPlease=false;
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text(_("this is a sample bank! select which ones to load:"));
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(_("All"))) {
|
||||
for (std::pair<DivSample*,bool>& i: pendingSamples) {
|
||||
i.second=true;
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(_("None"))) {
|
||||
for (std::pair<DivSample*,bool>& i: pendingSamples) {
|
||||
i.second=false;
|
||||
}
|
||||
}
|
||||
bool reissueSearch=false;
|
||||
|
||||
bool anySelected=false;
|
||||
float sizeY=ImGui::GetFrameHeightWithSpacing()*pendingSamples.size();
|
||||
if (sizeY>(canvasH-180.0*dpiScale))
|
||||
{
|
||||
sizeY=canvasH-180.0*dpiScale;
|
||||
if (sizeY<60.0*dpiScale) sizeY=60.0*dpiScale;
|
||||
}
|
||||
if (ImGui::BeginTable("PendingSamplesList",1,ImGuiTableFlags_ScrollY,ImVec2(0.0f,sizeY)))
|
||||
{
|
||||
if (sampleBankSearchQuery.empty())
|
||||
{
|
||||
for (size_t i=0; i<pendingSamples.size(); i++)
|
||||
{
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
String id=fmt::sprintf("%d: %s",(int)i,pendingSamples[i].first->name);
|
||||
if (pendingInsSingle)
|
||||
{
|
||||
if (ImGui::Selectable(id.c_str()))
|
||||
{
|
||||
pendingSamples[i].second=true;
|
||||
quitPlease=true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO:fixstyle from hereonwards
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if(ImGui::Checkbox(id.c_str(),&pendingSamples[i].second) && io.KeyShift)
|
||||
{
|
||||
for(int jj = (int)i - 1; jj >= 0; jj--)
|
||||
{
|
||||
if(pendingSamples[jj].second) //pressed shift and there's selected item above
|
||||
{
|
||||
for(int k = jj; k < (int)i; k++)
|
||||
{
|
||||
pendingSamples[k].second = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pendingSamples[i].second) anySelected=true;
|
||||
}
|
||||
}
|
||||
else //display search results
|
||||
{
|
||||
if(reissueSearch)
|
||||
{
|
||||
String lowerCase=sampleBankSearchQuery;
|
||||
|
||||
for (char& ii: lowerCase)
|
||||
{
|
||||
if (ii>='A' && ii<='Z') ii+='a'-'A';
|
||||
}
|
||||
|
||||
sampleBankSearchResults.clear();
|
||||
for (int j=0; j < (int)pendingSamples.size(); j++)
|
||||
{
|
||||
String lowerCase1 = pendingSamples[j].first->name;
|
||||
|
||||
for (char& ii: lowerCase1)
|
||||
{
|
||||
if (ii>='A' && ii<='Z') ii+='a'-'A';
|
||||
}
|
||||
|
||||
if (lowerCase1.find(lowerCase)!=String::npos)
|
||||
{
|
||||
sampleBankSearchResults.push_back(std::make_pair(pendingSamples[j].first, pendingSamples[j].second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i=0; i<sampleBankSearchResults.size(); i++)
|
||||
{
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
String id=fmt::sprintf("%d: %s",(int)i,sampleBankSearchResults[i].first->name);
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if(ImGui::Checkbox(id.c_str(),&sampleBankSearchResults[i].second) && io.KeyShift)
|
||||
{
|
||||
for(int jj = (int)i - 1; jj >= 0; jj--)
|
||||
{
|
||||
if(sampleBankSearchResults[jj].second) //pressed shift and there's selected item above
|
||||
{
|
||||
for(int k = jj; k < (int)i; k++)
|
||||
{
|
||||
sampleBankSearchResults[k].second = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sampleBankSearchResults[i].second) anySelected=true;
|
||||
}
|
||||
|
||||
for (size_t i=0; i<pendingSamples.size(); i++)
|
||||
{
|
||||
if(sampleBankSearchResults.size() > 0)
|
||||
{
|
||||
for (size_t j=0; j<sampleBankSearchResults.size(); j++)
|
||||
{
|
||||
if(sampleBankSearchResults[j].first == pendingSamples[i].first && sampleBankSearchResults[j].second && pendingSamples[i].first != NULL)
|
||||
{
|
||||
pendingSamples[i].second = true;
|
||||
if (pendingSamples[i].second) anySelected=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(!anySelected);
|
||||
if (ImGui::Button(_("OK"))) {
|
||||
quitPlease=true;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button(_("Cancel")) || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
for (std::pair<DivSample*,bool>& i: pendingSamples) {
|
||||
i.second=false;
|
||||
}
|
||||
quitPlease=true;
|
||||
}
|
||||
if (quitPlease)
|
||||
{
|
||||
ImGui::CloseCurrentPopup();
|
||||
int counter = 0;
|
||||
for (std::pair<DivSample*,bool>& i: pendingSamples)
|
||||
{
|
||||
if (!i.second)
|
||||
{
|
||||
delete i.first;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(counter == 0 && replacePendingSample)
|
||||
{
|
||||
*e->song.sample[curSample]=*i.first;
|
||||
replacePendingSample = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
e->addSamplePtr(i.first);
|
||||
}
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
|
||||
curSample = (int)e->song.sample.size() - 1;
|
||||
pendingSamples.clear();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
centerNextWindow(_("Import Raw Sample"),canvasW,canvasH);
|
||||
if (ImGui::BeginPopupModal(_("Import Raw Sample"),NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text(_("Data type:"));
|
||||
|
|
@ -7581,7 +7825,15 @@ bool FurnaceGUI::init() {
|
|||
#endif
|
||||
|
||||
compatFormats+="*.dmc ";
|
||||
compatFormats+="*.brr";
|
||||
compatFormats+="*.brr ";
|
||||
|
||||
compatFormats+="*.ppc ";
|
||||
compatFormats+="*.pps ";
|
||||
compatFormats+="*.pvi ";
|
||||
compatFormats+="*.pdx ";
|
||||
compatFormats+="*.pzi ";
|
||||
compatFormats+="*.p86 ";
|
||||
compatFormats+="*.p";
|
||||
audioLoadFormats[1]=compatFormats;
|
||||
|
||||
audioLoadFormats.push_back(_("NES DPCM data"));
|
||||
|
|
@ -7590,6 +7842,27 @@ bool FurnaceGUI::init() {
|
|||
audioLoadFormats.push_back(_("SNES Bit Rate Reduction"));
|
||||
audioLoadFormats.push_back("*.brr");
|
||||
|
||||
audioLoadFormats.push_back(_("PMD YM2608 ADPCM-B sample bank"));
|
||||
audioLoadFormats.push_back("*.ppc");
|
||||
|
||||
audioLoadFormats.push_back(_("PDR 4-bit AY-3-8910 sample bank"));
|
||||
audioLoadFormats.push_back("*.pps");
|
||||
|
||||
audioLoadFormats.push_back(_("FMP YM2608 ADPCM-B sample bank"));
|
||||
audioLoadFormats.push_back("*.pvi");
|
||||
|
||||
audioLoadFormats.push_back(_("MDX OKI ADPCM sample bank"));
|
||||
audioLoadFormats.push_back("*.pdx");
|
||||
|
||||
audioLoadFormats.push_back(_("FMP 8-bit PCM sample bank"));
|
||||
audioLoadFormats.push_back("*.pzi");
|
||||
|
||||
audioLoadFormats.push_back(_("PMD 8-bit PCM sample bank"));
|
||||
audioLoadFormats.push_back("*.p86");
|
||||
|
||||
audioLoadFormats.push_back(_("PMD OKI ADPCM sample bank"));
|
||||
audioLoadFormats.push_back("*.p");
|
||||
|
||||
audioLoadFormats.push_back(_("all files"));
|
||||
audioLoadFormats.push_back("*");
|
||||
|
||||
|
|
@ -8012,6 +8285,8 @@ FurnaceGUI::FurnaceGUI():
|
|||
snesFilterHex(false),
|
||||
modTableHex(false),
|
||||
displayEditString(false),
|
||||
displayPendingSamples(false),
|
||||
replacePendingSample(false),
|
||||
displayExportingROM(false),
|
||||
changeCoarse(false),
|
||||
mobileEdit(false),
|
||||
|
|
|
|||
|
|
@ -1593,7 +1593,7 @@ class FurnaceGUI {
|
|||
int sampleTexW, sampleTexH;
|
||||
bool updateSampleTex;
|
||||
|
||||
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile, sysSearchQuery, newSongQuery, paletteQuery;
|
||||
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile, sysSearchQuery, newSongQuery, paletteQuery, sampleBankSearchQuery;
|
||||
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport;
|
||||
String workingDirVGMExport, workingDirZSMExport, workingDirROMExport;
|
||||
String workingDirFont, workingDirColors, workingDirKeybinds;
|
||||
|
|
@ -1605,6 +1605,7 @@ class FurnaceGUI {
|
|||
String folderString;
|
||||
|
||||
std::vector<DivSystem> sysSearchResults;
|
||||
std::vector<std::pair<DivSample*,bool>> sampleBankSearchResults;
|
||||
std::vector<FurnaceGUISysDef> newSongSearchResults;
|
||||
std::vector<int> paletteSearchResults;
|
||||
FixedQueue<String,32> recentFile;
|
||||
|
|
@ -1620,6 +1621,7 @@ class FurnaceGUI {
|
|||
bool displayNew, displayExport, displayPalette, fullScreen, preserveChanPos, sysDupCloneChannels, sysDupEnd, noteInputPoly, notifyWaveChange;
|
||||
bool wantScrollListIns, wantScrollListWave, wantScrollListSample;
|
||||
bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString;
|
||||
bool displayPendingSamples, replacePendingSample;
|
||||
bool displayExportingROM;
|
||||
bool changeCoarse;
|
||||
bool mobileEdit;
|
||||
|
|
@ -2216,7 +2218,7 @@ class FurnaceGUI {
|
|||
maxUndoSteps(100),
|
||||
vibrationStrength(0.5f),
|
||||
vibrationLength(20),
|
||||
s3mOPL3(0),
|
||||
s3mOPL3(1),
|
||||
mainFontPath(""),
|
||||
headFontPath(""),
|
||||
patFontPath(""),
|
||||
|
|
@ -2373,6 +2375,7 @@ class FurnaceGUI {
|
|||
std::vector<DivCommand> cmdStream;
|
||||
std::vector<Particle> particles;
|
||||
std::vector<std::pair<DivInstrument*,bool>> pendingIns;
|
||||
std::vector<std::pair<DivSample*,bool>> pendingSamples;
|
||||
|
||||
std::vector<FurnaceGUISysCategory> sysCategories;
|
||||
|
||||
|
|
|
|||
|
|
@ -1203,28 +1203,63 @@ void FurnaceGUI::initSystemPresets() {
|
|||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
SUB_ENTRY(
|
||||
"Sega TeraDrive", {
|
||||
CH(DIV_SYSTEM_YM2612, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
SUB_SUB_ENTRY(
|
||||
"Sega TeraDrive (extended channel 3)", {
|
||||
CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
SUB_SUB_ENTRY(
|
||||
"Sega TeraDrive (CSM)", {
|
||||
CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
SUB_SUB_ENTRY(
|
||||
"Sega TeraDrive (DualPCM)", {
|
||||
CH(DIV_SYSTEM_YM2612_DUALPCM, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
SUB_SUB_ENTRY(
|
||||
"Sega TeraDrive (DualPCM, extended channel 3)", {
|
||||
CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
ENTRY(
|
||||
_("Sharp X1"), {
|
||||
CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=3")
|
||||
}
|
||||
}
|
||||
);
|
||||
SUB_ENTRY(
|
||||
_("Sharp X1 + FM Addon"), {
|
||||
CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=3"),
|
||||
CH(DIV_SYSTEM_YM2151, 1.0f, 0, "clockSel=2")
|
||||
}
|
||||
}
|
||||
);
|
||||
ENTRY(
|
||||
_("Sharp X68000"), {
|
||||
CH(DIV_SYSTEM_YM2151, 1.0f, 0, "clockSel=2"),
|
||||
CH(DIV_SYSTEM_MSM6258, 1.0f, 0, "clockSel=2")
|
||||
}
|
||||
}
|
||||
);
|
||||
ENTRY(
|
||||
_("FM-7"), {
|
||||
CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=12"),
|
||||
CH(DIV_SYSTEM_YM2203, 1.0f, 0, "clockSel=5")
|
||||
}
|
||||
}
|
||||
);
|
||||
SUB_ENTRY(
|
||||
_("FM-7 (extended channel 3)"), {
|
||||
|
|
|
|||
|
|
@ -4755,7 +4755,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
|
|||
settings.vibrationStrength=conf.getFloat("vibrationStrength",0.5f);
|
||||
settings.vibrationLength=conf.getInt("vibrationLength",20);
|
||||
|
||||
settings.s3mOPL3=conf.getInt("s3mOPL3",0);
|
||||
settings.s3mOPL3=conf.getInt("s3mOPL3",1);
|
||||
|
||||
settings.backupEnable=conf.getInt("backupEnable",1);
|
||||
settings.backupInterval=conf.getInt("backupInterval",30);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue