diff --git a/android/app/build.gradle b/android/app/build.gradle index 41d07937c..e19c78a58 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -15,8 +15,8 @@ android { } minSdkVersion 21 targetSdkVersion 26 - versionCode 161 - versionName "0.6pre6" + versionCode 162 + versionName "0.6pre7" externalNativeBuild { cmake { arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static", "-DWARNINGS_ARE_ERRORS=ON" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b8bde5cef..7f6a769d4 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ diff --git a/demos/multichip/invicibility_mmc5_n163_fds.fur b/demos/multichip/invicibility_mmc5_n163_fds.fur new file mode 100644 index 000000000..5faad8f0c Binary files /dev/null and b/demos/multichip/invicibility_mmc5_n163_fds.fur differ diff --git a/demos/nes/invicibility_mmc5_n163_fds.fur b/demos/nes/invicibility_mmc5_n163_fds.fur deleted file mode 100644 index ef9b05bf9..000000000 Binary files a/demos/nes/invicibility_mmc5_n163_fds.fur and /dev/null differ diff --git a/demos/pce/Warpdrive_Engage.fur b/demos/pce/Warpdrive_Engage.fur new file mode 100644 index 000000000..9bda4c918 Binary files /dev/null and b/demos/pce/Warpdrive_Engage.fur differ diff --git a/doc/2-interface/menu-bar.md b/doc/2-interface/menu-bar.md index 0027d6da7..822f38897 100644 --- a/doc/2-interface/menu-bar.md +++ b/doc/2-interface/menu-bar.md @@ -8,6 +8,7 @@ the menu bar allows you to select five menus: file, edit, settings, window and h - **open...**: opens the file picker, allowing you to select a song to open. - **open recent**: contains a list of the songs you've opened before. - **clear history**: this option erases the file history. + - **save**: saves the current song. - opens the file picker if this is a new song, or a backup. - **save as...**: opens the file picker, allowing you to save the song under a different name. @@ -29,12 +30,14 @@ the menu bar allows you to select five menus: file, edit, settings, window and h - Arcade (YM2151 + SegaPCM 5-channel compatibility) - Neo Geo CD (DefleMask 1.0+) - only use this option if you really need it. there are features which DefleMask does not support, like some effects and FM macros, so these will be lost. + - **export audio...**: export your song to a .wav file. see next section for more details. - **export VGM...**: export your song to a .vgm file. see next section for more details. - **export ZSM...**: export your song to a .zsm file. see next section for more details. - only available when there's a YM2151 and/or VERA. - **export command stream...**: export song data to a command stream file. see next section for more details. - this option is for developers. + - **add chip...**: add a chip to the current song. - **configure chip...**: set a chip's parameters. - for a list of parameters, see [7-systems](../7-systems/README.md). @@ -42,6 +45,7 @@ the menu bar allows you to select five menus: file, edit, settings, window and h - **Preserve channel positions**: enable this option to make sure Furnace does not auto-arrange/delete channels to compensate for differing channel counts. this can be useful for doing ports, e.g. from Genesis to PC-98. - **remove chip...**: remove a chip. - **Preserve channel positions**: same thing as above. + - **restore backup**: restore a previously saved backup. - Furnace keeps up to 5 backups of a song. - the backup directory is located in: @@ -49,6 +53,7 @@ the menu bar allows you to select five menus: file, edit, settings, window and h - macOS: `~/Library/Application Support/Furnace/backups` - Linux/other: `~/.config/furnace/backups` - this directory grows in size as you use Furnace. remember to delete old backups periodically to save space. + - **exit**: I think you know what this does. ## export audio @@ -131,6 +136,7 @@ it's not really useful, unless you're a developer and want to use a command stre - **undo**: reverts the last action. - **redo**: repeats what you undid previously. + - **cut**: moves the current selection in the pattern view to clipboard. - **copy**: copies the current selection in the pattern view to clipboard. - **paste**: inserts the clipboard's contents in the cursor position. @@ -147,11 +153,16 @@ it's not really useful, unless you're a developer and want to use a command stre - if the selection is tall, it will select the entire column. - if a column is already selected, it will select the entire channel. - if a channel is already selected, it will select the entire pattern. + - **operation mask**: this is an advanced feature. see [this page](../3-pattern/opmask.md) for more information. - **input latch**: this is an advanced feature. see [this page](../3-pattern/inputlatch.md) for more information. + - **note/octave up/down**: transposes notes in the current selection. + - **values up/down**: changes values in the current selection by ±1 or ±16. + - **transpose**: transpose notes or change values by a specific amount. + - **interpolate**: fills in gaps in the selection by interpolation between values. - **change instrument**: changes the instrument number in a selection. - **gradient/fade**: replace the selection with a "gradient" that goes from the beginning of the selection to the end. @@ -163,17 +174,22 @@ it's not really useful, unless you're a developer and want to use a command stre - **randomize**: replaces the selection with random values. - does not affect the note column. - **invert values**: `00` becomes `FF`, `01` becomes `FE`, `02` becomes `FD` and so on. + - **flip selection**: flips the selection so it is backwards. - **collapse/expand amount**: allows you to specify how much to collapse/expand in the next options. - **collapse**: shrinks the selected contents. - **expand**: expands the selected contents. + - **collapse pattern**: same as collapse, but affects the entire pattern. - **expand pattern**: same as expand, but affects the entire pattern. + - **collapse song**: same as collapse, but affects the entire song. - it also changes speeds and pattern length to compensate. - **expand song**: same as expand, but affects the entire song. - it also changes speeds and pattern length to compensate. + - **find/replace**: opens the Find/Replace window. see [this page](../3-pattern/find-replace.md) for more information. + - **clear**: allows you to mass-delete things like songs, instruments and the like. # settings @@ -202,9 +218,11 @@ it's not really useful, unless you're a developer and want to use a command stre - **chip manager**: shows/hides the Chip Manager window. - **compatibility flags**: shows/hides the Compatibility Flags window. - **song comments**: shows/hides the Song Comments window. -- **instrument editor**: shows/hides the Instrument Editor. + +- **instrument editor**: shows/hides the Instrument Editor - **wavetable editor**: shows/hides the Wavetable Editor. - **sample editor**: shows/hides the Sample Editor. + - **play/edit controls**: shows/hides the Play/Edit Controls. - **piano/input pad**: shows/hides the Piano/Input Pad window. - **oscilloscope (master)**: shows/hides the oscilloscope. diff --git a/doc/3-pattern/effects.md b/doc/3-pattern/effects.md index 6becb6c82..df1f85500 100644 --- a/doc/3-pattern/effects.md +++ b/doc/3-pattern/effects.md @@ -7,17 +7,15 @@ however, effects are continuous, which means you only need to type it once and t ## volume - `0Axy`: **Volume slide.** - - If `x` is 0 then this is a slide down. - - If `y` is 0 then this is a slide up. -- `F8xx`: **Single tick volume slide up.** -- `F9xx`: **Single tick volume slide down.** -- `F3xx`: **Fine volume slide up.** 64× slower than `0Axy`. -- `F4xx`: **Fine volume slide down.** 64× slower than `0Axy`. -- `FAxy`: **Fast volume slide.** 4× faster than `0Axy`. - - If `x` is 0 then this is a slide down. - - If `y` is 0 then this is a slide up. + - If `x` is 0 then this slides volume down by `y` each tick. + - If `y` is 0 then this slides volume up by `x` each tick. +- `FAxy`: **Fast volume slide.** same as `0Axy` above but 4× faster. +- `F3xx`: **Fine volume slide up.** same as `0Ax0` but 64× slower. +- `F4xx`: **Fine volume slide down.** same as `0A0x` but 64× slower. +- `F8xx`: **Single tick volume slide up.** adds `x` to volume on first tick only. +- `F9xx`: **Single tick volume slide down.** subtracts `x` from volume on first tick only. -- `07xy`: **Tremolo.** changes volume to be "wavy" with a sine LFO. `x` is the speed, while `y` is the depth. +- `07xy`: **Tremolo.** changes volume to be "wavy" with a sine LFO. `x` is the speed. `y` is the depth. - Tremolo is downward only. - Maximum tremolo depth is -60 volume steps. @@ -29,21 +27,22 @@ however, effects are continuous, which means you only need to type it once and t - `F1xx`: **Single tick pitch slide up.** - `F2xx`: **Single tick pitch slide down.** -- `03xx`: **Portamento.** slides the current note's pitch to the specified note. +- `03xx`: **Portamento.** slides the current note's pitch to the specified note. `x` is the slide speed. - A note _must_ be present for this effect to work. - `E1xy`: **Note slide up.** `x` is the speed, while `y` is how many semitones to slide up. - `E2xy`: **Note slide down.** `x` is the speed, while `y` is how many semitones to slide down. + - `EAxx`: **Toggle legato.** while on, notes instantly change the pitch of the currrently playing sound instead of starting it over. - `00xy`: **Arpeggio.** after using this effect the channel will rapidly switch between semitone values of `note`, `note + x` and `note + y`. -- `E0xx`: **Set arpeggio speed.** this sets the number of ticks between arpeggio values. +- `E0xx`: **Set arpeggio speed.** this sets the number of ticks between arpeggio values. default is 1. - `04xy`: **Vibrato.** changes pitch to be "wavy" with a sine LFO. `x` is the speed, while `y` is the depth. - Maximum vibrato depth is ±1 semitone. - `E3xx`: **Set vibrato direction.** `xx` may be one of the following: - - `00`: Up and down. + - `00`: Up and down. default. - `01`: Up only. - `02`: Down only. -- `E4xx`: **Set vibrato range** in 1/16th of a semitone. +- `E4xx`: **Set vibrato range** in 1/16th of a semitone. ## panning @@ -51,15 +50,15 @@ not all chips support these effects. - `08xy`: **Set panning.** changes stereo volumes independently. `x` is the left channel and `y` is the right one. - `88xy`: **Set rear panning.** changes rear channel volumes independently. `x` is the rear left channel and `y` is the rear right one. +- `81xx`: **Set volume of left channel** (from `00` to `FF`). +- `82xx`: **Set volume of right channel** (from `00` to `FF`). +- `89xx`: **Set volume of rear left channel** (from `00` to `FF`). +- `8Axx`: **Set volume of rear right channel** (from `00` to `FF`). - `80xx`: **Set panning (linear).** this effect behaves more like other trackers: - `00` is left. - `80` is center. - `FF` is right. -- `81xx`: **Set volume of left channel** (from `00` to `FF`). -- `82xx`: **Set volume of right channel** (from `00` to `FF`). -- `89xx`: **Set volume of rear left channel** (from `00` to `FF`). -- `8Axx`: **Set volume of rear right channel** (from `00` to `FF`). ## time @@ -67,12 +66,14 @@ not all chips support these effects. - `0Fxx`: **Set speed 2.** during alternating speeds or a groove, this sets the second speed. - `Cxxx`: **Set tick rate.** changes tick rate to `xxx` Hz (ticks per second). - - `xxx` may be from `000` to `3ff`. -- `F0xx`: **Set BPM.** changes tick rate according to beats per minute. + - `xxx` may be from `000` to `3FF`. +- `F0xx`: **Set BPM.** changes tick rate according to beats per minute. range is `01` to `FF`. -- `0Bxx`: **Jump to order.** this can be used to loop a song. -- `0Dxx`: **Jump to next pattern.** this can be used to shorten the current order. -- `FFxx`: **Stop song.** stops playback and ends the song. +- `0Bxx`: **Jump to order.** `x` is the order to play after the current row. + - this marks the end of a loop with order `x` as the loop start. +- `0Dxx`: **Jump to next pattern.** skips the current row and remainder of current order. + - this can be used to shorten the current order. +- `FFxx`: **Stop song.** stops playback and ends the song. `x` is ignored. ## note diff --git a/doc/7-systems/README.md b/doc/7-systems/README.md index d36480746..71285b521 100644 --- a/doc/7-systems/README.md +++ b/doc/7-systems/README.md @@ -27,6 +27,7 @@ this is a list of sound chips that Furnace supports, including effects. - [PC Engine/TurboGrafx-16](pce.md) - [PC Speaker](pcspkr.md) - [Philips SAA1099](saa1099.md) +- [Pokémon mini](pokemini.md) - [Capcom QSound](qsound.md) - [Ricoh RF5C68](ricoh.md) - [SegaPCM](segapcm.md) diff --git a/doc/7-systems/game-boy.md b/doc/7-systems/game-boy.md index 505de282f..eed8241af 100644 --- a/doc/7-systems/game-boy.md +++ b/doc/7-systems/game-boy.md @@ -2,7 +2,7 @@ the Nintendo Game Boy is one of the most successful portable game systems ever made. -with stereo sound, two pulse channels, a wave channel and a noise one it packed some serious punch. +with stereo sound, two pulse channels, a wave channel and a noise channel, it packed some serious punch. # effects @@ -20,3 +20,9 @@ with stereo sound, two pulse channels, a wave channel and a noise one it packed - `y` is the shift. - set to `0` to disable it. - `14xx`: **set sweep direction.** `0` is up and `1` is down. + +# links + +- [Gameboy sound hardware](https://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware) - detailed technical information + +- [GameBoy Sound Table](http://www.devrs.com/gb/files/sndtab.html) - note frequency table \ No newline at end of file diff --git a/doc/7-systems/pokemini.md b/doc/7-systems/pokemini.md new file mode 100644 index 000000000..142d49658 --- /dev/null +++ b/doc/7-systems/pokemini.md @@ -0,0 +1,7 @@ +# Pokémon Mini + +the Pokémon Mini is a ridiculously small handheld system from 2001. its single pulse channel has only three volume steps (full, half, and off)... but variable pulse width. + +# effects + +none. diff --git a/doc/8-advanced/chanosc-gradient.png b/doc/8-advanced/chanosc-gradient.png new file mode 100644 index 000000000..e8e7e73a7 Binary files /dev/null and b/doc/8-advanced/chanosc-gradient.png differ diff --git a/doc/8-advanced/chanosc.md b/doc/8-advanced/chanosc.md index 55e04a19d..f4b79ca75 100644 --- a/doc/8-advanced/chanosc.md +++ b/doc/8-advanced/chanosc.md @@ -1,16 +1,53 @@ # oscilloscope (per channel) -The "Oscilloscope (per channel)" dialog shows an individual oscilloscope for each channel during playback. +the "Oscilloscope (per channel)" dialog shows an individual oscilloscope for each channel during playback. ![oscilloscope per-channel configuration view](chanosc.png) -Right-clicking within the view will change it to the configuration view shown above: -- **Columns**: Sets the number of columns the view will be split into. -- **Size (ms)**: Sets what length of audio is visible in each oscilloscope. -- **Center waveform**: Does its best to latch to the channel's note frequency and centers the display. -- **Gradient**: (document this) -- The color selector sets the waveform color. Right-clicking on it pops up an option dialog: - - Select between the square selector and the color wheel selector. - - **Alpha bar**: Adds a transparency selector. -- The boxes below that are for selecting colors numerically by red-green-blue-alpha, hue-saturation-value-alpha, and HTML-style RGBA in hex. -- The OK button returns from options view to regular. +right-clicking within the view will change it to the configuration view shown above: +- **Columns**: arranges oscilloscopes into this many columns. +- **Size (ms)**: sets what length of audio is visible in each oscilloscope. +- **Center waveform**: does its best to latch the waveform to the channel's note frequency and centers the display. +- **Gradient**: see below. +- the color selector sets the waveform color. right-clicking on it pops up an option dialog: + - select between the square selector and the color wheel selector. + - **Alpha bar**: adds a transparency selector. +- the boxes below that are for selecting colors numerically by red-green-blue-alpha, hue-saturation-value-alpha, and HTML-style RGBA in hex. +- **Text format**: this string determins what text is shown in the top-left of each oscilloscope. it can be any text, and the following shortcodes will be replaced with information about the channel: + - `%c`: channel name + - `%C`: channel short name + - `%d`: channel number (starting from 0) + - `%D`: channel number (starting from 1) + - `%i`: instrument name + - `%I`: instrument number (decimal) + - `%x`: instrument number (hex) + - `%s`: chip name + - `%S`: chip ID + - `%v`: volume (decimal) + - `%V`: volume (percentage) + - `%b`: volume (hex) + - `%%`: percent sign +- The OK button returns from options view to the oscilloscopes. + +## gradient + +![oscilloscope per-channel gradient configuration view](chanosc-gradient.png) + +in this mode, the color selector is replaced by a square field onto which circular "stops" can be placed. each stop adds a soft circle of color. the resulting image is used to look up the oscilloscope color as determined by each axis. + +- right-click to place a stop. +- left-click on a stop to change its color. the color selector is the same as above, with two additions: + - **Distance**: the size of the circle. + - **Spread**: the size of the solid center of the circle. increasing it fills more of the circle with the target color. + +- **Background**: sets background color for entire field. +- **X Axis**: determines what the horizontal maps to, from left to right.\ + **Y Axis**: determines what the vertical maps to, from bottom to top. these can be set to the following: + - **None (0%)**: stays at the left or bottom. + - **None (50%)**: stays at the center. + - **None (100%)**: stays at the right or top. + - **Frequency**: changes color with note frequency. + - **Volume**: changes color with volume. + - **Channel**: changes color based on channel number. + - **Brightness**: {{document this}} + - **Note Trigger**: changes color when a new note is played. diff --git a/doc/8-advanced/chanosc.png b/doc/8-advanced/chanosc.png index 42c6be295..7536dad84 100644 Binary files a/doc/8-advanced/chanosc.png and b/doc/8-advanced/chanosc.png differ diff --git a/extern/imgui_patched/backends/imgui_impl_dx11.cpp b/extern/imgui_patched/backends/imgui_impl_dx11.cpp index 4977ba2d7..00ecb8e60 100644 --- a/extern/imgui_patched/backends/imgui_impl_dx11.cpp +++ b/extern/imgui_patched/backends/imgui_impl_dx11.cpp @@ -339,17 +339,22 @@ static void ImGui_ImplDX11_CreateFontsTexture() subResource.SysMemPitch = desc.Width * 4; subResource.SysMemSlicePitch = 0; bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); - IM_ASSERT(pTexture != nullptr); - // Create texture view - D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; - ZeroMemory(&srvDesc, sizeof(srvDesc)); - srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MipLevels = desc.MipLevels; - srvDesc.Texture2D.MostDetailedMip = 0; - bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->pFontTextureView); - pTexture->Release(); + if (pTexture != nullptr) { + // Create texture view + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; + ZeroMemory(&srvDesc, sizeof(srvDesc)); + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = desc.MipLevels; + srvDesc.Texture2D.MostDetailedMip = 0; + bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->pFontTextureView); + pTexture->Release(); + } else { + bd->pFontTextureView=NULL; + bd->pFontSampler=NULL; + return; + } } // Store our identifier @@ -609,13 +614,15 @@ void ImGui_ImplDX11_Shutdown() IM_DELETE(bd); } -void ImGui_ImplDX11_NewFrame() +bool ImGui_ImplDX11_NewFrame() { ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX11_Init()?"); if (!bd->pFontSampler) ImGui_ImplDX11_CreateDeviceObjects(); + + return bd->pFontSampler!=NULL; } //-------------------------------------------------------------------------------------------------------- diff --git a/extern/imgui_patched/backends/imgui_impl_dx11.h b/extern/imgui_patched/backends/imgui_impl_dx11.h index cee486f56..5f4dd7f99 100644 --- a/extern/imgui_patched/backends/imgui_impl_dx11.h +++ b/extern/imgui_patched/backends/imgui_impl_dx11.h @@ -19,7 +19,7 @@ struct ID3D11DeviceContext; IMGUI_IMPL_API bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context); IMGUI_IMPL_API void ImGui_ImplDX11_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplDX11_NewFrame(); +IMGUI_IMPL_API bool ImGui_ImplDX11_NewFrame(); IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data); // Use if you want to reset your rendering device without losing Dear ImGui state. diff --git a/extern/imgui_patched/backends/imgui_impl_opengl3.cpp b/extern/imgui_patched/backends/imgui_impl_opengl3.cpp index 2ff3e2621..c9bd8e171 100644 --- a/extern/imgui_patched/backends/imgui_impl_opengl3.cpp +++ b/extern/imgui_patched/backends/imgui_impl_opengl3.cpp @@ -208,6 +208,8 @@ #define GL_CALL(_CALL) _CALL // Call without error check #endif +#define GL_CALL_FALSE(_CALL) _CALL; { GLenum gl_err = glGetError(); if (gl_err != 0) return false; } + // OpenGL Data struct ImGui_ImplOpenGL3_Data { @@ -389,13 +391,14 @@ void ImGui_ImplOpenGL3_Shutdown() IM_DELETE(bd); } -void ImGui_ImplOpenGL3_NewFrame() +bool ImGui_ImplOpenGL3_NewFrame() { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplOpenGL3_Init()?"); if (!bd->ShaderHandle) - ImGui_ImplOpenGL3_CreateDeviceObjects(); + return ImGui_ImplOpenGL3_CreateDeviceObjects(); + return true; } static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) @@ -674,14 +677,14 @@ bool ImGui_ImplOpenGL3_CreateFontsTexture() // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) GLint last_texture; GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); - GL_CALL(glGenTextures(1, &bd->FontTexture)); - GL_CALL(glBindTexture(GL_TEXTURE_2D, bd->FontTexture)); - GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + GL_CALL_FALSE(glGenTextures(1, &bd->FontTexture)); + GL_CALL_FALSE(glBindTexture(GL_TEXTURE_2D, bd->FontTexture)); + GL_CALL_FALSE(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + GL_CALL_FALSE(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); #ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES - GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); + GL_CALL_FALSE(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); #endif - GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); + GL_CALL_FALSE(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); // Store our identifier io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); @@ -918,7 +921,9 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects() glGenBuffers(1, &bd->VboHandle); glGenBuffers(1, &bd->ElementsHandle); - ImGui_ImplOpenGL3_CreateFontsTexture(); + bool whatReturn=true; + + if (!ImGui_ImplOpenGL3_CreateFontsTexture()) whatReturn=false; // Restore modified GL state glBindTexture(GL_TEXTURE_2D, last_texture); @@ -927,7 +932,7 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects() glBindVertexArray(last_vertex_array); #endif - return true; + return whatReturn; } void ImGui_ImplOpenGL3_DestroyDeviceObjects() diff --git a/extern/imgui_patched/backends/imgui_impl_opengl3.h b/extern/imgui_patched/backends/imgui_impl_opengl3.h index 1c7666c81..328a145d0 100644 --- a/extern/imgui_patched/backends/imgui_impl_opengl3.h +++ b/extern/imgui_patched/backends/imgui_impl_opengl3.h @@ -29,7 +29,7 @@ // Backend API IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = nullptr); IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame(); +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_NewFrame(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); // (Optional) Called by Init/NewFrame/Shutdown diff --git a/papers/format.md b/papers/format.md index 2df15fcf3..78d62d489 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,7 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 162: Furnace 0.6pre7 - 161: Furnace 0.6pre6 - 160: Furnace dev160 - 159: Furnace dev159 @@ -309,6 +310,7 @@ size | description | - 0xc9: M114S - 16 channels | - 0xca: ZX Spectrum (beeper, QuadTone engine) - 5 channels | - 0xcb: Casio PV-1000 - 3 channels + | - 0xcc: K053260 - 4 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfc: Pong - 1 channel diff --git a/papers/multiplayer.md b/papers/multiplayer.md new file mode 100644 index 000000000..5f87bdba4 --- /dev/null +++ b/papers/multiplayer.md @@ -0,0 +1,404 @@ +# multiplayer protocol + +this is a concept! it has not been implemented yet! + +the Furnace protocol is described here. + +# information + +all numbers are little-endian. + +the following fields may be found in "size": +- `f` indicates a floating point number. +- `STR` is a UTF-8 zero-terminated string. +- `CFG` is the same as STR, but contains a config. +- `???` is an array of variable size. +- `S??` is an array of `STR`s. +- `1??` is an array of bytes. +- `2??` is an array of shorts. +- `4??` is an array of ints. + +two player IDs are reserved: +- 0: system +- 1: host (console) + +two usernames are reserved: +- SYSTEM +- HOST + +some characters are not allowed in usernames: 0x00-0x1f, `@`, 0x7f-0x9f, 0xd800-0xdfff, 0xfeff, 0xfffe and 0xffff. + +# header +``` +size | description +-----|------------------------------------ + 3 | "fur" header + 1 | packet type + 4 | sequence number +``` +the sequence number always starts at 0. + +# client to server packets (init) + +## 0x00: keep-alive + +this packet keeps a connection alive. +the server shall respond with a packet of type 0x00 (keep-alive). +if the client does not receive any packets during 30 seconds, it will disconnect from the server. +likewise, if the server does not receive any packets during 30 seconds, it will disconnect the client. + +## 0x01: start connection +``` +size | description +-----|------------------------------------ + 1 | reason + | - 0: information + | - 1: join + 3 | padding + 4 | client version + STR | host name (may be blank) +``` +after sending, you will receive a packet of type 0x01 (information), 0x02 (disconnect) or 0x03 (authenticate). + +## 0x02: disconnect +``` +size | description +-----|------------------------------------ + STR | reason +``` +## 0x03: auth response +``` +size | description +-----|------------------------------------ + 1 | type + | - 0: open + | - 1: password + | - 2: token + --- | **open response** + STR | username + --- | **password response** + 1 | password type + | - 0: plain text + | - 1: SHA-512 + STR | account name + ??? | password + --- | **token response** + STR | token +``` +# server to client packets (init) + +## 0x00: keep-alive + +this packet keeps a connection alive. it is a response to a client's keep-alive packet. + +## 0x01: information +``` +size | description +-----|------------------------------------ + 4 | server version + 2 | online players + | - if it is 65535, this information is concealed. + 2 | maximum players + | - 0 means unlimited. + STR | server version (string) + STR | server name + STR | server description + STR | project name +``` +the client may send a 0x00 (keep-alive) packet after receiving this one within 5 seconds. +connection is then closed. + +## 0x02: disconnect +``` +size | description +-----|------------------------------------ + STR | reason +``` +after being sent, the connection is closed. + +## 0x03: authenticate +``` +size | description +-----|------------------------------------ + 1 | authentication type + | - 0: open + | - 1: password + | - 2: token +``` +## 0x04: authentication success +``` +size | description +-----|------------------------------------ + 4 | player ID + STR | username + CFG | properties +``` +# client to server packets (session) + +## 0x10: request project + +the client may only send this once every minute. + +## 0x11: participate +``` +size | description +-----|------------------------------------ + 1 | status + | - 0: spectate + | - 1: join +``` +## 0x12: send chat message +``` +size | description +-----|------------------------------------ + STR | message +``` +## 0x13: send command +``` +size | description +-----|------------------------------------ + STR | command + 2 | number of arguments + S?? | arguments +``` +## 0x14: get player list + +no other information required. + +## 0x15: project submission request + +no other information required + +## 0x16: project submission information +``` +size | description +-----|------------------------------------ + 4 | project size + 32 | SHA-256 sum of project + STR | project name +``` +this is followed by several 0x17 (project data) packets representing a Furnace song. see [format.md](format.md) for more information. + +## 0x17: project submission data +``` +size | description +-----|------------------------------------ + 4 | offset + 4 | length + ??? | data... +``` +the client will send a packet with project size as offset and 0 as length to indicate end of data. +the server subsequently loads the project. + +# server to client packets (session) + +## 0x10: project information +``` +size | description +-----|------------------------------------ + 4 | project size + 32 | SHA-256 sum of project + STR | project name +``` +this is followed by several 0x13 (project data) packets representing a Furnace song. see [format.md](format.md) for more information. + +## 0x11: project data +``` +size | description +-----|------------------------------------ + 4 | offset + 4 | length + ??? | data... +``` +the server will send a packet with project size as offset and 0 as length to indicate end of data. +the client subsequently loads the project. + +## 0x12: participate status +``` +size | description +-----|------------------------------------ + 1 | status + | - 0: denied + | - 1: allowed + +## 0x13: message +``` +size | description +-----|------------------------------------ + 4 | player ID + 8 | time (seconds) + 4 | time (nanoseconds) + 4 | message ID + 1 | type + | - 0: chat, public + | - 1: chat, private + | - 2: notification, info + | - 3: notification, warning + | - 4: notification, urgent + STR | message +``` +## 0x14: system message +``` +size | description +-----|------------------------------------ + STR | message +``` +## 0x15: chat message edited +``` +size | description +-----|------------------------------------ + 4 | message ID + STR | message + | - an empty message means deleted. +``` +## 0x16: player list +``` +size | description +-----|------------------------------------ + 2 | number of players + --- | **player entry** (×players) + 4 | ID + 2 | latency (ms) + 1 | participating? + | - 0: no + | - 1: yes + 1 | status + | - 0: normal + | - 1: away + | - 2: busy + STR | name + STR | IP address + | - if empty, then server is not disclosing IP addresses. +``` +this is sent after receiving 0x14 (get player list). + +## 0x17: project submission request status +``` +size | description +-----|------------------------------------ + 1 | status + | - 0: denied + | - 1: allowed +``` +this is sent after a project submission request is accepted. +if the status is 1, the client shall submit a project. + +## 0x18: project submission complete +``` +size | description +-----|------------------------------------ + 1 | status + | - 0: error + | - 1: success + STR | additional information +``` +## 0x19: player joined +``` +size | description +-----|------------------------------------ + 4 | ID + STR | name +``` +## 0x1a: player left +``` +size | description +-----|------------------------------------ + 4 | ID +``` +# client to server packets (project) + +## 0x20: request orders + +## 0x21: request instrument + +## 0x22: request wavetable + +## 0x23: request sample + +## 0x24: request patterns + +## 0x25: request sub-song + +## 0x26: request song info + +## 0x27: request asset list + +## 0x28: request patchbay + +## 0x29: request grooves + +## 0x30: alter orders +``` +size | description +-----|------------------------------------ + 4 | transaction ID +``` +## 0x31: alter instrument + +## 0x32: alter wavetable + +## 0x33: alter sample + +## 0x34: alter pattern + +## 0x35: alter sub-song + +## 0x36: alter song info + +## 0x37: alter asset list + +## 0x38: alter patchbay + +## 0x39: alter grooves + +## 0x3a: alter chips + +## 0x3b: alter chip settings + +# server to client packets (project) + +## 0x20: orders + +## 0x21: instrument + +## 0x22: wavetable + +## 0x23: sample + +## 0x24: pattern + +## 0x25: sub-song + +## 0x26: song info + +## 0x27: asset list + +## 0x28: patchbay + +## 0x29: grooves + +## 0x30: transaction response +``` +size | description +-----|------------------------------------ + 1 | status + | - 0: error + | - 1: success + | - 2: success but request again + STR | additional information +``` +# client to server packets (interact) + +## 0x40: engine command + +## 0x41: playback + +# server to client packets (interact) + +## 0x40: engine command + +## 0x41: playback + +# client to server packets (extension) + +# server to client packets (extension) diff --git a/res/Info.plist b/res/Info.plist index 651e0837a..2bb0c814e 100644 --- a/res/Info.plist +++ b/res/Info.plist @@ -15,17 +15,17 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString - 0.6pre6 + 0.6pre7 CFBundleName Furnace CFBundlePackageType APPL CFBundleShortVersionString - 0.6pre6 + 0.6pre7 CFBundleSignature ???? CFBundleVersion - 0.6pre6 + 0.6pre7 NSHumanReadableCopyright NSHighResolutionCapable diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index c458cf9c4..acb7af66e 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -288,6 +288,9 @@ struct DivRegWrite { * - x is the instance ID * - 0xffffxx04: switch sample bank * - for use in VGM export + * - 0xffffxx05: set sample position + * - xx is the instance ID + * - data is the sample position * - 0xffffffff: reset */ unsigned int addr; diff --git a/src/engine/engine.h b/src/engine/engine.h index 2f2821573..d9772cd37 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -54,8 +54,10 @@ #define EXTERN_BUSY_BEGIN_SOFT e->softLocked=true; e->isBusy.lock(); #define EXTERN_BUSY_END e->isBusy.unlock(); e->softLocked=false; -#define DIV_VERSION "0.6pre6" -#define DIV_ENGINE_VERSION 161 +#define DIV_UNSTABLE + +#define DIV_VERSION "dev163" +#define DIV_ENGINE_VERSION 163 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 @@ -489,7 +491,7 @@ class DivEngine { void processRow(int i, bool afterDelay); void nextOrder(); void nextRow(); - void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, size_t bankOffset, bool directStream); + void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream); // returns true if end of song. bool nextTick(bool noAccum=false, bool inhibitLowLat=false); bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal); diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 53ad298ca..fcd969726 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -63,7 +63,7 @@ void DivPlatformGenesis::processDAC(int iRate) { for (int i=5; i<7; i++) { if (chan[i].dacSample!=-1) { DivSample* s=parent->getSample(chan[i].dacSample); - if (!isMuted[i] && s->samples>0) { + if (!isMuted[i] && s->samples>0 && chan[i].dacPossamples) { if (parent->song.noOPN2Vol) { chan[i].dacOutput=s->data8[chan[i].dacDirection?(s->samples-chan[i].dacPos-1):chan[i].dacPos]; } else { @@ -110,7 +110,7 @@ void DivPlatformGenesis::processDAC(int iRate) { chan[5].dacPeriod+=chan[5].dacRate; if (chan[5].dacPeriod>=iRate) { DivSample* s=parent->getSample(chan[5].dacSample); - if (s->samples>0) { + if (s->samples>0 && chan[5].dacPossamples) { if (!isMuted[5]) { if (chan[5].dacReady && writes.size()<16) { int sample; @@ -122,10 +122,6 @@ void DivPlatformGenesis::processDAC(int iRate) { urgentWrite(0x2a,(unsigned char)sample+0x80); chan[5].dacReady=false; } - } else { - if (chan[5].dacReady && writes.size()<16) { - urgentWrite(0x2a,0x80); - } } chan[5].dacPos++; if (!chan[5].dacDirection && (s->isLoopable() && chan[5].dacPos>=(unsigned int)s->loopEnd)) { @@ -597,6 +593,7 @@ void DivPlatformGenesis::muteChannel(int ch, bool mute) { isMuted[ch]=mute; if (ch>6) return; if (ch<6) { + if (ch==5) immWrite(0x2a,0x80); for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[ch]|opOffs[j]; DivInstrumentFM::Operator& op=chan[ch].state.op[j]; @@ -704,7 +701,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) { addWrite(0xffff0003,chan[c.chan].dacDirection); } } - chan[c.chan].dacPos=0; + if (chan[c.chan].setPos) { + chan[c.chan].setPos=false; + } else { + chan[c.chan].dacPos=0; + } chan[c.chan].dacPeriod=0; if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false); @@ -927,6 +928,12 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (dumpWrites) addWrite(0xffff0003,chan[c.chan].dacDirection); break; } + case DIV_CMD_SAMPLE_POS: + if (c.chan<5) c.chan=5; + chan[c.chan].dacPos=c.value; + chan[c.chan].setPos=true; + if (dumpWrites) addWrite(0xffff0005,chan[c.chan].dacPos); + break; case DIV_CMD_LEGATO: { if (c.chan==csmChan) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 210fca9d0..c9de0493f 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -57,6 +57,7 @@ class DivPlatformGenesis: public DivPlatformOPN { int dacDelay; bool dacReady; bool dacDirection; + bool setPos; unsigned char sampleBank; signed char dacOutput; Channel(): @@ -70,6 +71,7 @@ class DivPlatformGenesis: public DivPlatformOPN { dacDelay(0), dacReady(true), dacDirection(false), + setPos(false), sampleBank(0), dacOutput(0) {} }; diff --git a/src/engine/platform/pv1000.cpp b/src/engine/platform/pv1000.cpp index bf03df405..0d7d728d0 100644 --- a/src/engine/platform/pv1000.cpp +++ b/src/engine/platform/pv1000.cpp @@ -42,7 +42,7 @@ void DivPlatformPV1000::acquire(short** buf, size_t len) { short samp=d65010g031_sound_tick(&d65010g031,1); buf[0][h]=samp; for (int i=0; i<3; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=d65010g031.out[i]<<1; + oscBuf[i]->data[oscBuf[i]->needle++]=MAX(d65010g031.out[i]<<2,0); } } } diff --git a/src/engine/platform/sound/ymfm/ymfm_opn.cpp b/src/engine/platform/sound/ymfm/ymfm_opn.cpp index 25d921a95..a8cc198a3 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opn.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_opn.cpp @@ -141,6 +141,7 @@ template bool opn_registers_base::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask) { assert(index < REGISTERS); + if (index >= REGISTERS) return false; // writes in the 0xa0-af/0x1a0-af region are handled as latched pairs // borrow unused registers 0xb8-bf/0x1b8-bf as temporary holding locations diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 5b8c2d9e3..fa0446ca6 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -236,9 +236,12 @@ void DivPlatformVERA::tick(bool sysTick) { if (s->samples>0) { if (s->isLoopable()) { // Inform the export process of the loop point for this sample - addWrite(67,s->loopStart&0xff); - addWrite(67,(s->loopStart>>8)&0xff); - addWrite(67,(s->loopStart>>16)&0xff); + int tmp_ls=(s->loopStart<<1); // for stereo + if (chan[16].pcm.depth16) + tmp_ls<<=1; // for 16 bit + addWrite(67,tmp_ls&0xff); + addWrite(67,(tmp_ls>>8)&0xff); + addWrite(67,(tmp_ls>>16)&0xff); } while (true) { short tmp_l=0; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 17275678c..a2f215efa 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -24,7 +24,7 @@ constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; -void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, size_t bankOffset, bool directStream) { +void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream) { unsigned char baseAddr1=isSecond?0xa0:0x50; unsigned char baseAddr2=isSecond?0x80:0; unsigned short baseAddr2S=isSecond?0x8000:0; @@ -620,15 +620,35 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write pendingFreq[streamID]=write.val; } else { DivSample* sample=song.sample[write.val]; - w->writeC(0x95); - w->writeC(streamID); - w->writeS(write.val); // sample number - w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags + int pos=sampleOff8[write.val&0xff]+setPos[streamID]; + int len=(int)sampleLen8[write.val&0xff]-setPos[streamID]; + + if (len<0) len=0; + + if (setPos[streamID]!=0) { + if (len<=0) { + w->writeC(0x94); + w->writeC(streamID); + } else { + w->writeC(0x93); + w->writeC(streamID); + w->writeI(pos); + w->writeC(1|((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())?0x80:0)|(sampleDir[streamID]?0x10:0)); // flags + w->writeI(len); + } + } else { + w->writeC(0x95); + w->writeC(streamID); + w->writeS(write.val); // sample number + w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags + } + if (sample->isLoopable() && !sampleDir[streamID]) { - loopTimer[streamID]=sample->length8; + loopTimer[streamID]=len; loopSample[streamID]=write.val; } playingSample[streamID]=write.val; + setPos[streamID]=0; } } break; @@ -642,16 +662,36 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write loopFreq[streamID]=realFreq; if (pendingFreq[streamID]!=-1) { DivSample* sample=song.sample[pendingFreq[streamID]]; - w->writeC(0x95); - w->writeC(streamID); - w->writeS(pendingFreq[streamID]); // sample number - w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags + int pos=sampleOff8[pendingFreq[streamID]&0xff]+setPos[streamID]; + int len=(int)sampleLen8[pendingFreq[streamID]&0xff]-setPos[streamID]; + + if (len<0) len=0; + + if (setPos[streamID]!=0) { + if (len<=0) { + w->writeC(0x94); + w->writeC(streamID); + } else { + w->writeC(0x93); + w->writeC(streamID); + w->writeI(pos); + w->writeC(1|((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())?0x80:0)|(sampleDir[streamID]?0x10:0)); // flags + w->writeI(len); + } + } else { + w->writeC(0x95); + w->writeC(streamID); + w->writeS(pendingFreq[streamID]); // sample number + w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags + } + if (sample->isLoopable() && !sampleDir[streamID]) { - loopTimer[streamID]=sample->length8; + loopTimer[streamID]=len; loopSample[streamID]=pendingFreq[streamID]; } playingSample[streamID]=pendingFreq[streamID]; pendingFreq[streamID]=-1; + setPos[streamID]=0; } break; } @@ -665,6 +705,41 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case 3: // set sample direction sampleDir[streamID]=write.val; break; + case 5: // set sample pos + setPos[streamID]=write.val; + + if (playingSample[streamID]!=-1 && pendingFreq[streamID]==-1) { + // play the sample again + DivSample* sample=song.sample[playingSample[streamID]]; + int pos=sampleOff8[playingSample[streamID]&0xff]+setPos[streamID]; + int len=(int)sampleLen8[playingSample[streamID]&0xff]-setPos[streamID]; + + if (len<0) len=0; + + if (setPos[streamID]!=0) { + if (len<=0) { + w->writeC(0x94); + w->writeC(streamID); + } else { + w->writeC(0x93); + w->writeC(streamID); + w->writeI(pos); + w->writeC(1|((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())?0x80:0)|(sampleDir[streamID]?0x10:0)); // flags + w->writeI(len); + } + } else { + w->writeC(0x95); + w->writeC(streamID); + w->writeS(playingSample[streamID]); // sample number + w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags + } + + if (sample->isLoopable() && !sampleDir[streamID]) { + loopTimer[streamID]=len; + loopSample[streamID]=playingSample[streamID]; + } + } + break; } } return; @@ -1082,6 +1157,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p int songTick=0; unsigned int sampleOff8[256]; + unsigned int sampleLen8[256]; unsigned int sampleOffSegaPCM[256]; SafeWriter* w=new SafeWriter; @@ -1102,6 +1178,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p bool sampleDir[DIV_MAX_CHANS]; int pendingFreq[DIV_MAX_CHANS]; int playingSample[DIV_MAX_CHANS]; + int setPos[DIV_MAX_CHANS]; std::vector chipVol; std::vector delayedWrites[DIV_MAX_CHIPS]; std::vector> sortedWrites; @@ -1121,6 +1198,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p loopSample[i]=-1; pendingFreq[i]=-1; playingSample[i]=-1; + setPos[i]=0; sampleDir[i]=false; } @@ -1379,7 +1457,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p willExport[i]=true; CHIP_VOL(6,1.0); CHIP_VOL(0x86,1.7); - writeDACSamples=true; } else if (!(hasOPN&0x40000000)) { isSecond[i]=true; willExport[i]=true; @@ -1873,6 +1950,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p // initialize sample offsets memset(sampleOff8,0,256*sizeof(unsigned int)); + memset(sampleLen8,0,256*sizeof(unsigned int)); memset(sampleOffSegaPCM,0,256*sizeof(unsigned int)); // write samples @@ -1881,6 +1959,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p DivSample* sample=song.sample[i]; logI("setting seek to %d",sampleSeek); sampleOff8[i]=sampleSeek; + sampleLen8[i]=sample->length8; sampleSeek+=sample->length8; } @@ -2016,9 +2095,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeC(0x67); w->writeC(0x66); w->writeC(0xc0+i); - w->writeI(writeRF5C68[i]->getSampleMemUsage()+8); - w->writeI(writeRF5C68[i]->getSampleMemCapacity()); - w->writeI(0); + w->writeI(writeRF5C68[i]->getSampleMemUsage()+2); + w->writeS(0); w->write(writeRF5C68[i]->getSampleMem(),writeRF5C68[i]->getSampleMemUsage()); } if (writeMSM6295[i]!=NULL && writeMSM6295[i]->getSampleMemUsage()>0) { @@ -2281,7 +2359,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p for (int i=0; i& writes=disCont[i].dispatch->getRegisterWrites(); for (DivRegWrite& j: writes) { - performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i],pendingFreq,playingSample,bankOffset[i],directStream); + performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i],directStream); writeCount++; } writes.clear(); @@ -2321,7 +2399,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p lastOne=i.second.time; } // write write - performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],pendingFreq,playingSample,bankOffset[i.first],directStream); + performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i.first],directStream); // handle global Furnace commands writeCount++; diff --git a/src/engine/zsm.cpp b/src/engine/zsm.cpp index 7cedc6d8e..04bf68b91 100644 --- a/src/engine/zsm.cpp +++ b/src/engine/zsm.cpp @@ -318,6 +318,7 @@ void DivZSM::flushWrites() { } pcmCache.resize(pcmCache.size()>>1); pcmCtrlDCCache&=(unsigned char)~0x10; // clear stereo bit + pcmLoopPointCache>>=1; // halve the loop point } } } else { // 8-bit @@ -334,6 +335,7 @@ void DivZSM::flushWrites() { } pcmCache.resize(pcmCache.size()>>1); pcmCtrlDCCache&=(unsigned char)~0x10; // clear stereo bit + pcmLoopPointCache>>=1; // halve the loop point } } } @@ -365,9 +367,9 @@ void DivZSM::flushWrites() { inst.loopPoint=pcmLoopPointCache; inst.isLooped=pcmIsLooped; pcmInsts.push_back(inst); - pcmIsLooped=false; - pcmLoopPointCache=0; } + pcmIsLooped=false; + pcmLoopPointCache=0; } if (extCmd0Len>63) { // this would be bad, but will almost certainly never happen logE("ZSM: extCmd 0 exceeded maximum length of 63: %d",extCmd0Len); diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 65b54d2a5..809f214b7 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -149,6 +149,14 @@ void FurnaceGUI::drawChanOsc() { ImGui::EndTable(); } + ImGui::Text("Amplitude"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderFloat("##COSAmp",&chanOscAmplify,0.0f,2.0f)) { + if (chanOscAmplify<0.0f) chanOscAmplify=0.0f; + if (chanOscAmplify>2.0f) chanOscAmplify=2.0f; + } + ImGui::Checkbox("Gradient",&chanOscUseGrad); if (chanOscUseGrad) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ffecb3c94..dd760e0fc 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5759,6 +5759,17 @@ bool FurnaceGUI::loop() { introPos=12.0; } +#ifdef DIV_UNSTABLE + { + ImDrawList* dl=ImGui::GetForegroundDrawList(); + ImVec2 markPos=ImVec2(canvasW-ImGui::CalcTextSize(DIV_VERSION).x-6.0*dpiScale,4.0*dpiScale); + ImVec4 markColor=uiColors[GUI_COLOR_TEXT]; + markColor.w=0.67f; + + dl->AddText(markPos,ImGui::ColorConvertFloat4ToU32(markColor),DIV_VERSION); + } +#endif + layoutTimeEnd=SDL_GetPerformanceCounter(); // backup trigger @@ -6265,14 +6276,14 @@ bool FurnaceGUI::init() { settings.renderBackend="SDL"; e->setConf("renderBackend","SDL"); e->saveConf(); - lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace."); + lastError=fmt::sprintf("could not init renderer!\r\nthe render backend has been set to a safe value. please restart Furnace."); } else { lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError()); if (!settings.renderDriver.empty()) { settings.renderDriver=""; e->setConf("renderDriver",""); e->saveConf(); - lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace."); + lastError+=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace."); } } return false; @@ -6362,16 +6373,16 @@ bool FurnaceGUI::init() { if (!rend->init(sdlWin)) { if (settings.renderBackend!="SDL") { settings.renderBackend="SDL"; - e->setConf("renderBackend",""); + e->setConf("renderBackend","SDL"); e->saveConf(); - lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace."); + lastError=fmt::sprintf("could not init renderer!\r\nthe render backend has been set to a safe value. please restart Furnace."); } else { lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError()); if (!settings.renderDriver.empty()) { settings.renderDriver=""; e->setConf("renderDriver",""); e->saveConf(); - lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace."); + lastError+=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace."); } } return false; @@ -6666,6 +6677,10 @@ bool FurnaceGUI::finish() { return true; } +void FurnaceGUI::requestQuit() { + quit=true; +} + FurnaceGUI::FurnaceGUI(): e(NULL), renderBackend(GUI_BACKEND_SDL), diff --git a/src/gui/gui.h b/src/gui/gui.h index 9bab661da..e3fe68eb4 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2307,6 +2307,7 @@ class FurnaceGUI { bool loop(); bool finish(); bool init(); + void requestQuit(); FurnaceGUI(); }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 0e851f9ea..6715e56b6 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2378,10 +2378,37 @@ void FurnaceGUI::drawInsEdit() { bool opsAreMutable=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM); if (ImGui::BeginTabItem("FM")) { + DivInstrumentFM& fmOrigin=(ins->type==DIV_INS_OPLL && ins->fm.opllPreset>0 && ins->fm.opllPreset<16)?opllPreview:ins->fm; + + bool isPresent[4]; + int isPresentCount=0; + memset(isPresent,0,4*sizeof(bool)); + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_VRC7) { + isPresent[3]=true; + } else if (e->song.system[i]==DIV_SYSTEM_OPLL || e->song.system[i]==DIV_SYSTEM_OPLL_DRUMS) { + isPresent[(e->song.systemFlags[i].getInt("patchSet",0))&3]=true; + } + } + if (!isPresent[0] && !isPresent[1] && !isPresent[2] && !isPresent[3]) { + isPresent[0]=true; + } + for (int i=0; i<4; i++) { + if (isPresent[i]) isPresentCount++; + } + int presentWhich=0; + for (int i=0; i<4; i++) { + if (isPresent[i]) { + presentWhich=i; + break; + } + } + if (ImGui::BeginTable("fmDetails",3,ImGuiTableFlags_SizingStretchSame)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableNextRow(); switch (ins->type) { case DIV_INS_FM: @@ -2453,14 +2480,14 @@ void FurnaceGUI::drawInsEdit() { break; } case DIV_INS_OPLL: { - bool dc=ins->fm.fms; - bool dm=ins->fm.ams; + bool dc=fmOrigin.fms; + bool dm=fmOrigin.ams; bool sus=ins->fm.alg; ImGui::TableNextColumn(); ImGui::BeginDisabled(ins->fm.opllPreset!=0); - P(CWSliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable + P(CWSliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&fmOrigin.fb,&_ZERO,&_SEVEN)); rightClickable if (ImGui::Checkbox(FM_NAME(FM_DC),&dc)) { PARAMETER - ins->fm.fms=dc; + fmOrigin.fms=dc; } ImGui::EndDisabled(); ImGui::TableNextColumn(); @@ -2469,7 +2496,7 @@ void FurnaceGUI::drawInsEdit() { } ImGui::BeginDisabled(ins->fm.opllPreset!=0); if (ImGui::Checkbox(FM_NAME(FM_DM),&dm)) { PARAMETER - ins->fm.ams=dm; + fmOrigin.ams=dm; } ImGui::EndDisabled(); ImGui::TableNextColumn(); @@ -2477,30 +2504,6 @@ void FurnaceGUI::drawInsEdit() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - bool isPresent[4]; - int isPresentCount=0; - memset(isPresent,0,4*sizeof(bool)); - for (int i=0; isong.systemLen; i++) { - if (e->song.system[i]==DIV_SYSTEM_VRC7) { - isPresent[3]=true; - } else if (e->song.system[i]==DIV_SYSTEM_OPLL || e->song.system[i]==DIV_SYSTEM_OPLL_DRUMS) { - isPresent[(e->song.systemFlags[i].getInt("patchSet",0))&3]=true; - } - } - if (!isPresent[0] && !isPresent[1] && !isPresent[2] && !isPresent[3]) { - isPresent[0]=true; - } - for (int i=0; i<4; i++) { - if (isPresent[i]) isPresentCount++; - } - int presentWhich=0; - for (int i=0; i<4; i++) { - if (isPresent[i]) { - presentWhich=i; - break; - } - } - if (ImGui::BeginCombo("##LLPreset",opllInsNames[presentWhich][ins->fm.opllPreset])) { if (isPresentCount>1) { if (ImGui::BeginTable("LLPresetList",isPresentCount)) { @@ -2578,11 +2581,26 @@ void FurnaceGUI::drawInsEdit() { // update OPLL preset preview if (ins->fm.opllPreset>0 && ins->fm.opllPreset<16) { - const opll_patch_t* patchROM=OPLL_GetPatchROM(opll_type_ym2413); + const opll_patch_t* patchROM=NULL; + + switch (presentWhich) { + case 1: + patchROM=OPLL_GetPatchROM(opll_type_ymf281); + break; + case 2: + patchROM=OPLL_GetPatchROM(opll_type_ym2423); + break; + case 3: + patchROM=OPLL_GetPatchROM(opll_type_ds1001); + break; + default: + patchROM=OPLL_GetPatchROM(opll_type_ym2413); + break; + } const opll_patch_t* patch=&patchROM[ins->fm.opllPreset-1]; - opllPreview.alg=0; + opllPreview.alg=ins->fm.alg; opllPreview.fb=patch->fb; opllPreview.fms=patch->dm; opllPreview.ams=patch->dc; @@ -2604,8 +2622,6 @@ void FurnaceGUI::drawInsEdit() { } } - DivInstrumentFM& fmOrigin=(ins->type==DIV_INS_OPLL && ins->fm.opllPreset>0 && ins->fm.opllPreset<16)?opllPreview:ins->fm; - ImGui::BeginDisabled(!willDisplayOps); if (settings.fmLayout==0) { int numCols=15; diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp index d3bd13069..26bfd0937 100644 --- a/src/gui/render/renderDX11.cpp +++ b/src/gui/render/renderDX11.cpp @@ -290,8 +290,7 @@ void FurnaceGUIRenderDX11::clear(ImVec4 color) { } bool FurnaceGUIRenderDX11::newFrame() { - ImGui_ImplDX11_NewFrame(); - return true; + return ImGui_ImplDX11_NewFrame(); } void FurnaceGUIRenderDX11::createFontsTexture() { diff --git a/src/gui/render/renderGL.cpp b/src/gui/render/renderGL.cpp index b16f04975..38fa4966b 100644 --- a/src/gui/render/renderGL.cpp +++ b/src/gui/render/renderGL.cpp @@ -233,8 +233,7 @@ void FurnaceGUIRenderGL::clear(ImVec4 color) { } bool FurnaceGUIRenderGL::newFrame() { - ImGui_ImplOpenGL3_NewFrame(); - return true; + return ImGui_ImplOpenGL3_NewFrame(); } void FurnaceGUIRenderGL::createFontsTexture() { diff --git a/src/main.cpp b/src/main.cpp index e58a455ac..0aa4c9b4f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,7 +36,10 @@ typedef HRESULT (WINAPI *SPDA)(PROCESS_DPI_AWARENESS); #else +#include #include + +struct sigaction termsa; #endif #include "cli/cli.h" @@ -356,6 +359,14 @@ void reportError(String what) { } #endif +#ifndef _WIN32 +#ifdef HAVE_GUI +static void handleTermGUI(int) { + g.requestQuit(); +} +#endif +#endif + // TODO: CoInitializeEx on Windows? // TODO: add crash log int main(int argc, char** argv) { @@ -646,6 +657,13 @@ int main(int argc, char** argv) { g.setFileName(fileName); } +#ifndef _WIN32 + sigemptyset(&termsa.sa_mask); + termsa.sa_flags=0; + termsa.sa_handler=handleTermGUI; + sigaction(SIGTERM,&termsa,NULL); +#endif + g.loop(); logI("closing GUI."); g.finish();