Compare commits

..

350 Commits

Author SHA1 Message Date
Denny Sheirer
2e37f1e687 Updates gradle wrapper to 6.3. Updates embeded java to JDK 14 for building platform releases. (#729)
Co-authored-by: Denny <denny@denny-desktop>
2020-04-04 09:22:13 -04:00
Denny Sheirer
5e8f2d0523 Updates build to 0.4.0 final and updates CHANGELOG revision history. (#727)
Co-authored-by: Denny <denny@denny-desktop>
2020-04-04 08:19:29 -04:00
crsawyer
9fb6c33319 Fix HackRF-JawBreaker and HackRF sample conversion (#725) 2020-04-04 08:02:17 -04:00
Denny Sheirer
fdecac4230 Resovles #721 Suppress RID values of 0. (#723)
Co-authored-by: Denny <denny@denny-desktop>
2020-04-01 05:05:28 -04:00
Denny Sheirer
85c27a6cca Resolves #719 P25 Phase 2 display channel number (#722)
Co-authored-by: Denny <denny@denny-desktop>
2020-04-01 03:28:26 -04:00
Denny Sheirer
57f56d31a2 #713 Update Version 0.4.0 Beta 8 (#714)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 12:06:23 -04:00
Denny Sheirer
df25675d3f Resolves #707 separate DTMF tones of the same value get merged together (#712)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 12:03:47 -04:00
Denny Sheirer
277a84702a Resolves #580 channel map selection and editing (#711)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 11:52:39 -04:00
Denny Sheirer
c651296378 Resolves #709 MPT1327 no audio on traffic channels. (#710)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 11:33:15 -04:00
Denny Sheirer
866d25e86a #705 Updates release version 0.4.0 beta 7 (#706)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 06:17:06 -04:00
Denny Sheirer
5e62f1b889 Resolves #677 Streaming Audio Metadata UNKNOWN/UNKNOWN (#704)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 05:58:21 -04:00
Denny Sheirer
e9d524ad14 Resolves #699 Channel Multi-Frequency rotation delay user preference does not persist. (#703)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 05:53:58 -04:00
Denny Sheirer
158c8b4b8a #701 P25 Phase 2 Tone Metadata and Channel State (#702)
* Resolves #701 Updates P25P2 Tone metadata handling and P25P2 channel state management.
2020-03-28 05:51:11 -04:00
Denny Sheirer
41df207bb9 Resolves #694. Address null pointer when no system audio devices are available. (#695)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-23 04:45:06 -04:00
Denny Sheirer
692ffabe82 Resolves #692. Updates Java requirement to version 13 and JavaFX libraries to version 13. (#693)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-22 09:30:43 -04:00
Denny
944ad5a571 Updates version to 0.4.0 beta 6 2020-03-22 06:39:53 -04:00
Denny
5ca70d3940 Removes logging from invalid channel type values. 2020-03-22 06:07:36 -04:00
Denny Sheirer
904752e3dd Resolves #688. Updates HackRF Sample Rate options and sets usable bandwidth for the tuner to 75% of the sample rate. (#689)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-22 05:31:56 -04:00
Denny Sheirer
cf61798466 Resolves #685. Resolves several issues with P25 Phase 2 channel state and call handling. (#687)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-21 14:42:04 -04:00
Denny Sheirer
fd72f459f9 Resolves #685. Resolves several issues with P25 Phase 2 channel state and call handling. (#686)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-21 14:31:26 -04:00
Denny Sheirer
1bc462b300 Resolves #676 Do Not Monitor feature not working. (#684)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-21 05:32:12 -04:00
Denny Sheirer
1c6f91f101 Resolves #682 NPE when logging channel events with no alias list specified. (#683)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-20 11:15:53 -04:00
Denny
a7c28f066a Resolved NPE with tone updates. 2020-03-20 09:14:10 -04:00
Denny Sheirer
604c8ab6e5 Resolves #680 Audio Do Not Monitor is not working. Adds audio segment drop tone to indicate that an audio playback is dropped because the do not monitor setting was applied after the audio segment started. (#681)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-20 08:31:35 -04:00
Denny Sheirer
23a3ee39cc Resolves #678 AudioChannelPanel threading issue. (#679)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-20 05:21:49 -04:00
Denny Sheirer
fa16db129b #660 Updates audio processing subsystem to use audio segments. Updates audio playback, recording and streaming to use audio segments. Adds support to UserPreferences for sound card selection and audio tone insertion during playback. Adds record preference for WAVE or MP3. Resolves issues with metadata in recordings. (#675) 2020-03-19 06:35:05 -04:00
Denny
de249c448b Removes debug logging. 2020-02-29 11:02:32 +00:00
Denny
e1bc56c500 Resolves issue with build script where OS-specific JavaFX libraries were not included in build images. 2020-02-23 08:35:49 -05:00
Denny Sheirer
d1d115d11f Updates build to version 0.4.0 beta 5 (#668) 2020-02-23 09:49:00 +00:00
Allegiance Zone pusher
aca41b4f6e alias in event log.diff (#659) 2020-02-22 20:38:37 +00:00
whelgeson
1e3ae996ac Implementing some build script cleanup work. (#664)
- Adding jitpack to import libs directly from the git repos
 - Removing binary imports from repo
 - Fixing Gradle 7 upgrade warnings
2020-02-22 20:31:29 +00:00
Denny Sheirer
b3b2e74688 Resolves #663. Updates P25 phase 2 Decoder to detect PLL lock errors for +/-90 and 180 out-of-phase. Adds support for broadcasting phase 2 channel frequency error calculations to support tuner auto-correction for frequency errors. Removes adaptive PLL bandwidth support from Phase 1 and uses a fixed PLL bandwidth for both phase 1 and phase 2 decoders. Updates the Phase 2 symbol timing gain with results from extensive testing of PLL bandwidth and symbol timing combinations. (#666) 2020-02-22 20:09:24 +00:00
Denny Sheirer
44237bec5c Resolves issue where user added icons were not displaying on windows OS. (#658) 2020-02-16 12:22:27 +00:00
Denny Sheirer
53b44f44e3 Updates gradle build to use version-specific root folder names for OS-specific image products. (#657) 2020-02-16 11:50:20 +00:00
Denny
9af7a184ad Utility for calculating PLL phase lock sync pattern derivatives 2020-02-14 06:11:15 -05:00
Denny Sheirer
9c8d87ec64 #651 Updates audio broadcaster to decrement pending replay count so that pending recordings don't cause a memory leak when the streaming server is not available. (#656) 2020-02-09 10:54:46 -05:00
Denny Sheirer
4c34968d28 #654 Warn users when USB bus speeds are exceeded. (#655)
Updates logging to track max usb speeds per bus and warns user when any USB bus combined tuner data rates exceeds 70% of the max bus rate.
2020-02-09 10:51:55 -05:00
Denny Sheirer
2d71f5173a #652 USB Tuner Error Handling Enhancements (#653)
-Separates USB timeout event processing to a dedicated thread
-Updates USB transfer buffer processor to better detect errors and auto-restart on error (max 3 times)
-Updates Tuner to add an error state and SourceManager to not select tuners with an error state
-Updates ChannelProcessingManager to auto-shutdown channels sourced from tuners set with error state
-Improves logging of USB related errors.
2020-02-09 05:40:04 -05:00
Denny Sheirer
2e942113c6 Resolves #644 NoClassDefFound error when Icecast metadata updater runs. Updates build to version 0.4.0 Beta 4 (#645) 2020-01-12 04:57:38 -05:00
Denny Sheirer
e20540ccc6 #630 Update gradle build to allow runtime image builds without locally installed JDKs. Updates build version to 0.4.0 beta 3 (#643) 2020-01-11 07:01:17 -05:00
Denny Sheirer
8d7e0e9f46 #641 Updates P25 Talkgroup and Radio Id valid ranges to allow value of 0. (#642) 2020-01-11 06:13:44 -05:00
Denny Sheirer
cc5d94f58d #639 Updates all streaming metadata to include radio identifier value as the from value. (#640) 2020-01-11 06:06:08 -05:00
Denny Sheirer
26197e433c #576 P25 Activity Summary Hex and Decimal Network Values (#638)
* #576 Updates P25 activity summaries to list values in hex and decimal.  Resolves issue with P25 explicit channel uplink frequency calculations.  Resolves issue with Motorola base station id messages and traffic channel messages.  Resolves issue with RFSS link control message control channel frequency calculations.  Resolves issue with error when using recording tuners.
2020-01-11 05:33:38 -05:00
Denny Sheirer
f54b4dfd0e 576 Update P25 Activity Summary To Provide Network Values in Hex and Decimal (#637)
* #576 Updates P25 activity summaries to list values in hex and decimal.  Resolves issue with P25 explicit channel uplink frequency calculations.  Resolves issue with Motorola base station id messages and traffic channel messages.  Resolves issue with RFSS link control message control channel frequency calculations.  Resolves issue with error when using recording tuners.
2020-01-11 05:23:47 -05:00
Denny Sheirer
085ba52294 #632 Resolves issue with audio recordings and from identifiers stemming from changes to the way radio IDs are handled. (#634) 2020-01-05 15:48:47 -05:00
Denny Sheirer
b8c24680a5 Resolves #628. Adds P25 talkgroup alias identifier conversions to radio identifiers for any talkgroups or ranges that have values greater than 65,535. Updates playlist version to 4. (#629) 2019-12-22 08:07:47 -05:00
Denny Sheirer
c658582157 Resolves #620. Separates radio identifiers from talkgroups for APCO-25. Updates alias editor with two new options for radio id and radio id range. (#627) 2019-12-22 07:30:48 -05:00
Denny Sheirer
83e518d503 Resolves #625. Updates handling of recording tuners. (#626) 2019-12-21 09:22:04 -05:00
Denny Sheirer
2163d0f822 Update README.md 2019-12-21 08:38:41 -05:00
Denny Sheirer
34c920eede Update README.md 2019-12-21 08:32:54 -05:00
Denny Sheirer
35b0b5ef9b Resolves #623 issue with P25 channels where uplink/downlink use different frequency bands. (#624) 2019-12-21 07:08:05 -05:00
Denny Sheirer
08672c33bd Update README.md 2019-12-20 05:46:24 -05:00
Denny Sheirer
e2651d6c7c Resolves #621 P25 Phase 2 Audio Streaming issue. (#622) 2019-12-20 04:55:37 -05:00
Denny Sheirer
851a4e12d2 Creates StuffBitsMessage to account for processing tail bits in P25 control channel stream. (#618) 2019-12-15 14:56:31 -05:00
Denny Sheirer
b7c66ec844 Updates build version to 0.4.0 beta 2. (#617)
Updates radio reference library to 15.1.2 to resolve issue with logback config collision.
Updates OS build names to better reflect processor types: x86_64 (64-bit) or x86_32 (32-bit)
2019-12-15 07:12:26 -05:00
Denny Sheirer
7522a3a820 Resolves Icecast HTTP metadata updating issue. Resolves issue with Shoutcast V2 metadata not correctly wrapped in xml tag. (#615) 2019-12-15 06:25:51 -05:00
Denny Sheirer
6a0402d1cf Resolves #609 P25 Phase 2 call events are not chaining together and are causing streaming events in the event panel. (#614) 2019-12-13 09:01:40 -05:00
Denny Sheirer
5309af84f7 Resolves #612 P25P2 bit recording filenames indicated incorrect decoder type. (#613) 2019-12-13 05:38:06 -05:00
Denny Sheirer
8d8d77fa11 Resolves #610. P25P2 Editor Doesn't accept scramble parameter values. (#611) 2019-12-13 05:18:30 -05:00
Denny Sheirer
284cf07e97 Update README.md
Adds link to Discord server
2019-11-25 20:05:35 -05:00
Denny Sheirer
16414c7585 Update README.md 2019-11-24 06:18:07 -05:00
Denny Sheirer
70c9eed0bf Update README.md 2019-11-23 16:40:59 -05:00
Denny Sheirer
f49b1fef17 Update README.md 2019-11-23 16:40:03 -05:00
Denny Sheirer
3fe070987a Resolves issue with clunky process for adding recording tuners. (#605) 2019-11-23 12:55:12 -05:00
Denny Sheirer
00d5610213 Resolves issue with the multi-frequency tuner source and P25/MPT1327 (#603)
Resolves issue with the multi-frequency tuner source and P25/MPT1327 (#603) rotating channel frequency configurations failing to maintain lock
on the channel.

Converted the ChannelRotationMonitor from using identifier updates to
now using DecoderStateEvents so that the ChannelState can supply a
steady stream of channel state events to the monitor.
2019-11-23 10:19:23 -05:00
Denny Sheirer
d3c1a605df Resolves issue with application version display in title bar (#601) 2019-11-22 09:47:09 -05:00
Denny Sheirer
85df151a96 Updates map tile fetcher to use TLSv1.2 when connecting to (#599)
openstreetmap.  Java13 uses TLSv1.3 by default and that's not supported
by the tile server.
2019-11-22 09:21:28 -05:00
Denny Sheirer
8915797aac Updates gradle build to use gradle 6 and Java 13.0.1 (#596) 2019-11-19 19:41:01 -05:00
Denny
03a5d71a95 Updates recording tuner to accept center frequency commands. 2019-11-18 19:42:01 -05:00
Denny Sheirer
4158d4ac10 Resolves #570 map tile data not downloading.
Also resolves issue with the default map loading icon pointing to an old image reference.
2019-11-17 11:41:41 -05:00
Denny Sheirer
f934676bd0 #296 APCO25 Phase II Decoder Implementation (#589)
P25 Phase 2 Decoder implementation.
2019-11-17 06:56:52 -05:00
Denny
d5b802e794 Updates next version to 0.4.0 beta 1. 2019-02-17 14:51:57 -05:00
Denny Sheirer
139227e46d Resolves #535 (#540)
Updates Icecast HTTP (2.4) streaming client to perpetually attempt to
reconnect to the server, even if there are (temp) errors.
2019-02-17 06:12:11 -05:00
Denny Sheirer
2394d51a51 Resolves #537 (#539)
Updates application title bar with currently displayed tuner.
2019-02-17 05:49:26 -05:00
Denny Sheirer
d9fa2bea5b Resolves #533. Updates broadcastify stream editor to use new (#538)
radio reference client library and adds support for storing user
credentials in user preferences.

Removes legacy apache axis SOAP and WSDL libraries calls to radio
reference web services -- not required by the new library.
2019-02-17 05:34:31 -05:00
Denny Sheirer
0cb360eeed Resovles #531 updates gradle build script to remove extra jfx runtimes (#532) 2019-02-02 15:17:11 +00:00
Denny Sheirer
ea472f873f Resolves #517 NPE thrown by TalkgroupRangeEditor (#530) 2019-02-02 14:24:23 +00:00
Denny Sheirer
8cbdbd527e Resolves #525 NPE in P25 activity summary report. (#529) 2019-02-02 13:40:57 +00:00
Denny Sheirer
ff0d893bd9 Updates build script to use latest beryx runtime zip plugin and adds (#528)
validation to check for each of the platform-specific JDKs before
executing the runtimeZip task.
2019-02-02 13:37:09 +00:00
Denny
7b924d0486 Removes debug logging 2019-01-27 20:21:46 -05:00
Denny
712eb07f4b Updates build to 0.4.0 alpha 8 2019-01-27 19:59:04 -05:00
Denny Sheirer
d5c3ecc7df Resolves #519 (#520)
- Clears stale patch groups
- Reworks the audio playback panel label update code to properly update
  talkgroups and aliases in concert with the audio playback.
- Removes code that was truncating playback aliases at 32 characters.
2019-01-28 00:58:01 +00:00
Denny Sheirer
c4bf089667 502 icecast http connect error (#518)
* Resolves #502

Resolves issue with connecting to icecast http streaming server.

The issue stems from Icecast HTTP (2.4+) servers not sending a full
HTTP OK response (missing header tokens) to a connection request.

Prior to the upgrade to apache mina 2.0.19, we received a specific
exception and handled it and set the status to connected.

After the upgrade, apache mina is now sending an array index out of
bounds exception when it decodes the incomplete http response.

So, we now catch that specific exception, inspect the http response
hex bytes and set the status to connected.
2019-01-27 14:00:57 +00:00
Denny Sheirer
378b03e06e 515 Patch Group Manager Cross Linking of Talk and Patch Groups (#516)
* Stores minimal main application window location and size/layout settings
to allow application to be restored to the same size/location on
restart.

* Removes talkgroup swapping from the patch group manager which may have
been causing talkgroup <> patch group cross-linking on certain systems.
2019-01-27 12:25:08 +00:00
Denny Sheirer
34235382fb 513 persist application layout across restarts (#514)
* Stores minimal main application window location and size/layout settings
to allow application to be restored to the same size/location on
restart.

* Adds column width monitor to all tables in application that stores the
column width settings in the user preferences so that on restarting
the application the table column widths are restored to the previous
widths.
2019-01-27 12:05:20 +00:00
Denny Sheirer
c05aff0768 Updates alias controller so that when adding an alias to a filtered (#512)
column view it doesn't throw an error.
2019-01-26 13:03:59 +00:00
Denny Sheirer
f0c3e0fd1a Adds logging/warning when alias list contains talkgroups or talkgroup (#511)
range collisions or overlaps.
2019-01-26 11:38:42 +00:00
Denny Sheirer
d601cbda0e Resolves issue with missing case statements in talkgroup format classes. (#509) 2019-01-26 11:11:41 +00:00
Denny Sheirer
2653dc6b38 Removes debug logging. (#508) 2019-01-26 11:02:43 +00:00
Denny Sheirer
48cbbfc656 Updates beryx gradle plugin to resolve issue where launcher script (#503)
did not like '&' ampersand in the execution path.
2019-01-23 09:21:16 +00:00
Denny
d6892b9203 Updates build version to 0.4.0 alpha 7 2019-01-21 04:23:42 -05:00
Denny Sheirer
59f0844fcc 499 Audio Playback Talkgroup Display (#500)
* Resolves issue with non-aliased talkgroup values not displaying in
audio playback (left/right/mono) windows.

* Adds buildSdr build target back into gradle script.
2019-01-21 09:22:31 +00:00
Denny Sheirer
43b285a50a 485 Upgrade Project and Build to Java 11 (#496)
* Updates gradle to 5.1.1

* Updated to run application from intellij using installed Java 11.
Resolves issues with Java 9 deprecations.

* Updates gradle build dependencies and other deprecation issues.

* Updates gradle to produce OS-specific builds.

* Updates the beryx runtime plugin to 1.1.1 to resolve issue with zipping
each of the target distributions.

* Import cleanup

* Resolves issue with Look and Feel error on Max OSX
Resolves issue with building OSX package
Resolves issue with excessive logging about JMBE library.
2019-01-20 18:11:36 +00:00
Denny Sheirer
4e92a6e350 Resolves #494 (#495)
Adds support to AM and FM decoders to record all audio produced by the
decoder.
2019-01-20 15:14:56 +00:00
Denny Sheirer
25d387f3cf 492 Formatted Talkgroup and TalkgroupRange Editors (#493)
* Updates Talkgroup and Talkgroup ranges and editors to support formatted
talkgroup values.

* Updates talkgroup format masks to allow spaces (ie incomplete values)
 in talkgroup values.
2019-01-20 14:30:54 +00:00
Denny Sheirer
52e3d9d20d Adds exception handler to DFT processor to catch InterruptedException (#491)
begin thrown by the FFT library on shutdown.
2019-01-19 10:29:05 +00:00
Denny Sheirer
1e37a63d34 Adds a new JMBE Audio Library preference to the user preferences editor (#489)
so that users can set the library path once and it will persist across
application upgrades so that they do NOT have to copy the library to
their application install with each upgrade.
2019-01-19 10:19:18 +00:00
Denny Sheirer
e51f485a2e Resolves NPE thrown when audio recording metadata is null. (#486) 2019-01-12 09:43:54 -05:00
Denny Sheirer
9378254a38 Updates codebase to use jIconFont icons. (#484)
Removes Ikonli library.
2019-01-12 07:17:51 -05:00
Denny Sheirer
4ab37735e8 Adds support for Motorola Opcode 07 Deny Response message. (#482) 2019-01-12 04:42:52 -05:00
Denny Sheirer
85ca66f656 Update README.md 2019-01-08 04:12:58 -05:00
Denny
d716b0be1c Updates build version to 0.4.0 alpha 6 2019-01-08 04:12:14 -05:00
Denny
70444c1337 Temporarily resolves issue with Ikonli icons that require Java 11. 2019-01-07 04:31:30 -05:00
Denny Sheirer
d90e149021 Adds support to MPT1327 decoder for rotating control channels. (#477) 2019-01-06 07:47:08 -05:00
Denny Sheirer
7510664e00 Update README.md 2019-01-06 07:25:59 -05:00
Denny Sheirer
5481fb7d4d 61 Rotating Control Channel Support - P25 Decoder (#475)
* Initial work on add support for rotating control channels.

* Creates multiple frequency editor control.

* Updates channel source editor to allow user to select multi-frequency
configuration and add multiple frequencies.

* Adds full support for rotating control channels to the P25 channelizer.
Users define multiple frequencies in the channel source configuration.
The decoder monitors the state of the channel for a CONTROL state.  If
the state does not stay in the CONTROL state for a user-specified delay
value, then the decoder rotates to the next frequency in the list.  This
behavior continues until the decoder state once again changes to CONTROL.

This is NOT a 'scanner' feature.
2019-01-06 06:59:04 -05:00
Denny Sheirer
3a39ae76ba Updates alias list to provide a list of aliases for an identifier so that (#474)
we can account for multi-alias identifiers like patch groups and ensure
that patch group alias intentions are carried throughout the system for
recording, streaming, etc.

Updates all consumers of alias list data to ensure it can operate with
a list of aliases versus an assumed single alias.

Refactors APCO25 unit/user status identifiers from a base status class
so that alias list can support status identifiers.
2018-12-31 07:24:25 -05:00
Denny Sheirer
08ea5ab7cb 465 Wave Recording Audio Metadata (#473)
* Initial work on updating wave audio metadata to use identifier collection.

* Cleanup
2018-12-31 06:01:59 -05:00
Denny Sheirer
3fa7401a2b 467 Alias List Update On Alias Delete/Change (#470)
* Updates playlist to fully clean aliases from the alias list when deleted
and to perform a delete and re-add on any alias that changes, to ensure
that the alias list is kept fully up to date.

Removed site id as an alias identifier.

Add support for converting ltr-net uid's to talkgroups in playlist
updater and updated playlist to version 3 to ensure anyone using current
codebase will have their playlists fully updated.

Moved MIN, UniqueID and Site ID to the legacy package and updated
label descriptions to signify that they are no longer supported.
2018-12-31 05:36:54 -05:00
Denny
fce10960fb Updates alias list to remove talkgroup/range alias identifier types. 2018-12-27 08:09:55 -05:00
Denny Sheirer
10b6a08624 Updates alias list to support talkgroup range and patch groups. (#463) 2018-12-27 07:52:38 -05:00
Denny Sheirer
5960343637 Updates PacketMessage to expose to/from identifiers to include the LLID, (#460)
and the to/from IPV4 addresses.
2018-12-27 06:57:18 -05:00
Denny Sheirer
347a71f097 444 Expose Application Directories via User Preferences (#458)
* Refactors DirectoryPreference from PLaylistPreference.
Adds editor to PreferenceEditor for directories.
Organizes preference editor tree.

* Adds recording directory preference to editor and updates recording
manager to dynamically use this property for recordings.

* Adds event logs directory preference to editor and updates event log
manager to dynamically use this property for event logging.

* Adds application log directory preference.  Updates logging to use user
preference for application log directory.  Moves logging initialization
into the main application class and removes file based logging configs
from the logback.xml configuration file.

* Refactors application logging into its own class and adds prefeence
update monitoring to dynamically stop/start the logger in the new
directory location.

* Adds streaming and screen capture directories to the user preferences
editor and updates streaming and screen captures to dynamically adapt
to user changes.
2018-12-27 06:12:03 -05:00
Denny Sheirer
5b5a1431b4 Adds toFront() to channel auto start frame to force the window to the front (#457) 2018-12-25 06:48:26 -05:00
Denny Sheirer
efd811407e Adds tuner preference editor with channelizer type options. Updates (#456)
tuners to use the preference to select either polyphase or heterodyne
channelizers.
2018-12-25 06:43:10 -05:00
Denny Sheirer
b67d4d92dc Identifier Framework and Message/Event Processing Overhaul (#455)
* WIP on new P25 message framer.

* WIP on new P25 message framer.

* Adds new 1/2 and 3/4 rate trellis decoders.  Still trying to chase
down why there is a sync pattern mismatch in the NID buffer queue.

* WIP - new p25 message framer works correctly now.  PDU's are dectected
and decoded using the 1/2 and 3/4 rate trellis decoders.  Packet
Sequences are assembled.  Need to add a new packet sequence message
factory that can detect Motorola Auto Registration Service (ARS)
formatted UDP/ID packets and SNDCP command packets.

* WIP - removes nullify on channel results queue.

* WIP - updates message framer for TSBK 1/2/3 processing.

* WIP removing P25 sync pattern and nid bits from all message parsers.

* WIP - merges identifier framework branch changes into this branch.

* WIP on updating link control words to new format and identifier framework.

* WIP - implements rest of link control words for extended formats

* WIP - initial work on ISP/OSP TSBK message restructuring.

* WIP - adds more TSBK ISP and OSP messages

* WIP - finished updating most of the TSBK ISP and OSP messages.  Converted
all messages to no longer include the NID (nac + dataunitID) so that
each message can simply hold the parsed values.  Removed AliasList as
an argument to each message and updated all messges to use the new
IIdentifer and IAPCO25Channel interfaces.

Removed legacy TDU and LDU link control messages and replaced with a
single LinkControlWord class hierarchy.

P25 decoder state is not yet updated for the new message structure.

PDU message parsing is not fully implemented.

* WIP - adds some Multi-Block trunking control messages with Alternate
and Unconfirmed formats.

* WIP - Fully implements AMBTC and UMBTC PDU message parsers.

Some package restructuring.

* WIP - Resolves Link control parsing in LDU1 and 2 messages.

* WIP - finishes updating TSBK and Link Control messages.  Adds support
for moto link control messages for patch groups.

* WIP - finishes updating TSBK and Link Control messages.  Adds support
for moto link control messages for patch groups.  Adds support for moto
ARS service, IPV4 decoding, and UDP/IP packets.

* WIP - finishes SNDCP packet data with header parsing.  Compressed IP or
UDP headers are not yet supported, but are logged as such.

* WIP - converts all talkgroup identifiers to integers and updates
alias list to create a per-protocol map of talkgroup id's to alias.

Inserts a legacy talkgroup (ltr, p25, passport, mpt1327, mdc, etc.)
conversion utility to convert existing legacy (string) talkgroups to
new integer talkgroup format.

Creates an audio packet manager to insert metadata into audio packets
before streaming packets for playback, recording, and/or streaming.

Creates identifier collection to manage current identifiers in each of
the channel and decoder states.

Creates simplified channel metadata to maintain the now playing display.

Moves channel attributes (alias list, system, site, name) as configuration
identifiers in the channel state.

Many other refactoring and updates.

* WIP - numerous updates to push use of identifier collection throughout
the system.  Updates the channel metadata and audio packet processing
logic.  Implements a large portion of the P25 decode state processing.
Updates traffic channel management.  Adds user preferences for timestamp
and identifier formatting, however doesn't add a user interface yet.

* WIP - initial implementation of user preferences editor window.

* WIP - updates preferences editor window to close and dispose window but
retain javafx event thread running.

* WIP - fully updates p25 decoder state and traffic channel processing to
use the new identifier framework.

Cleans up channel event processing and removes channel model as the sole
broadcaster of channel events.  Channel processing manager now listens
for all start/stop events and requestors dispatch event directly to the
channel processing manager.

Resolves issue in airspy that was causing user interface to lock-up.  The
issue appears to occur when the dongle is plugged into a usb port that
can't fully support the data rate.  sdrtrunk now recognizes these errors
and warns the user that they should use another usb port.

* WIP - updates alias action manager and adds new talkgroup range alias identifier.

Updates playlist to incorporate a version number so that the playlist
updater only runs when needed, to update a playlist to the correct
version format.

Updates user preferences to use the google event bus to broadcast
preference update notifications to remove the need for listeners to
register directly on the user preferences instance.

* WIP - updates alias id editors to flag invalid values and moves legacy
alias ids to legacy package.

Updates event logging configuration to add traffic channel decode events
and decoded message logging.  Removes binary message logging since that
is now replaced with demodulated bitstream recording.

* Removes system properties from user preferences.  Preferences are stored
in the OS-dependent settings store (windows - registry)

* Updates MapService to use new PlottableDecodeEvent instead of monitoring
all of the decoded messages.

Updates Fleetsync decoder and related messages to identifier framework.

* Updates LTR-Net, Fleetsync and LJ1200 decoders to new identifier
framework and adds support for user preferences for formatting of
LTR-Net and fleetsync talkgroup identifiers.

Breaks LTR-Net messages into various message types and uses a new
message factory.

Removes alias list from message processing for ltr-net, fleetsync and
lj1200.

* Updates LTR-Standard messages to Identifier framework.

* Removes Plottable and IPlottable from codebase.

* wip

* WIP on MPT-1327 messages.

* Updates MPT1327 decoder to identifier framework and new traffic channel
manager.  This decoder still needs to be updated with a new message
parser factory and a sync detector like what is now used for P25.

* Updates MDC1200 decoder and message to identifier framework.

* Updates Tait1200 and Passport decoders to identifier framework.

* Merges changes from master branch.

* Removes legacy alias support classes.  Updates passport message processor
to only pass valid messages.  Adds support to playlist updater for
converting passport mobile ids to become passport talkgroup values.

* Updates P25 decoder state and channel state to better track allocated
traffic channels.  Resolves issue in timestamp manager for trackig
message timestamps. Resolves issue in airspy tuner that was causing
the tuner to stop sending samples to the application.

* Update P25 Decoder state to process SNDCP management messages and
PDU messages.

Updates P25 activity summary to display network and site details.

Resolves issue with overlay panel that prevented enabled channels from
rendering correctly when zoomed in.

* Creates a new FilePreferences as part of the user preferences to begin
the transition of SystemProperties over to user preferences and consolidate
file preferences in one location.

Updates playlist manager to use the new file preferences and to use
default.xml as the new default playlist name, following conversion of
a playlist_v2.xml format to the new format.

In the near future, the user will be able to edit file locations and to
create/use other playlists.

Resolved an issue with all decoder states where not all identifiers were
being included in decode events.
2018-12-24 09:08:34 -05:00
whelgeson
735fca16d6 Fix for issue #441 Audio Recording Stops After Encountering Error (#449)
* Fix for issue #441.
Method doc reads that the method is thread-safe, but there exist no locking to ensure thread safety on call into recycle method. Modifying the decrementUserCount method to block other callers to ensure thread-safe access.

This appears to be a hot area of code and there may be a performance hit by doing this, please double check my work here.

* Moving synchronized to recycle method as it's accessed by two different methods that could potentially cause an unsafe access race condition.

* Revert - synchronizing recycle does not protect against multiple recycle calls on the same object.
2018-12-16 19:12:41 -05:00
whelgeson
72fe72c833 Fixing issue #353 Forward Slash in site or channel name causes recording error (#448)
* Fixing issue #353
Moved replaceIllegalCharacters method into StringUtils.
Modified classes that write out to disk to call this method when saving a file.

* Implementing suggested changes from comments in PR #448
2018-12-16 19:10:56 -05:00
Denny Sheirer
4a4d1d4b2e Merge pull request #447 from whelgeson/440-Update-java4usb-library-to-1.3
Bumping usb4java to 1.3.0
2018-12-15 04:59:08 -05:00
Denny Sheirer
2ed7074c0d Merge pull request #446 from whelgeson/442-Runtime-error-on-Java-11
Bumping Jide to 3.6.18 to fix Java 11 reflections error
2018-12-15 04:53:01 -05:00
billh
b5a895c266 Bumping usb4java to 1.3.0 2018-12-13 23:28:18 -06:00
billh
b57d7b5c0e Bumping jide to 3.6.18 to fix Java 11 reflections error 2018-12-13 23:20:32 -06:00
Denny Sheirer
cb12d314d0 Merge pull request #436 from DSheirer/387-audio-10-minute-time-limit
Resolves #387 (P25) 10 second/10 minute audio recording time limit.
2018-10-08 04:59:10 -04:00
Denny
eb79d9e037 Resolves #387 10 second/10 minute audio recording time limit.
Updated audio packet wave recorder to correctly dequeue audio packets
to ensure that the overflowable buffer queue accounting process was
working correctly.

Updated overflowable buffer queue's poll() method to correctly decrement
the queue size counter.

Resolved issue where the mutable metadata was being prematurely reset
and causing the end audio packet to be mis-identified.  This resulted
in the recording manager not stopping the recording on command with the
timeout mechanism kicking in after a brief delay.
2018-10-08 04:58:21 -04:00
Denny
d95c78c0f1 Updates version to 0.4.0 alpha 5 2018-09-23 05:51:28 -04:00
Denny Sheirer
aee5ece8d2 Merge pull request #430 from DSheirer/429-polyphase-channelizer-25khz-channel-rate
429 polyphase channelizer 25khz channel rate
2018-09-23 05:50:18 -04:00
Denny
ba358a7d48 Adjusted costas loop gain values. Updated channel filter values to mitigate adjacent channel interference. 2018-09-23 05:34:41 -04:00
Denny
09b209496b Resolves issue with NPE in 3/4 rate decoder. 2018-09-22 09:19:15 -04:00
Denny Sheirer
cc209683bd Merge pull request #428 from DSheirer/377-npe-in-trellis-decoder
377 NPE in P25 3/4 Rate Trellis Decoder
2018-09-22 02:53:16 -04:00
Denny
2771c28dba Resolves issue with NPE in 3/4 rate decoder. 2018-09-22 02:52:36 -04:00
Denny
d752917073 Resolves #427. Moves the module(s) reset operation to the stop() method
so that the modules are reset after they are shut down, in order to
prepare for processing chain reuse.

Previously, the modules were reset upon startup, but after any traffic
channel metadata had already been transferred to the channel state,
causing the metadata to be lost and allowing initial audio packets to
stream without any metadata and related streaming/monitoring rules.
2018-09-20 04:01:45 -04:00
Denny
28bc02e172 Updates channel state to ensure teardown processing is completed during
a call end and/or during a commanded stop.
2018-09-19 04:25:37 -04:00
Denny
0f6044d48b Updates mutable metadata to reset the buffer overflow flag between uses. 2018-09-18 19:15:06 -04:00
Denny Sheirer
58ecb83e2c Merge pull request #425 from DSheirer/417-channel-overflow-state-processing
417 Channel Overflow State Processing - Part 2
2018-09-16 10:23:15 -04:00
Denny
9e1de922e4 Resolves issue with overflow state processing. Displays the overflow
state in the Message field of the channel metadata table column instead
of displaying it in the channel state column.
2018-09-16 10:22:27 -04:00
Denny Sheirer
84d0b827d8 Merge pull request #422 from DSheirer/421-nbfm-25-khz-channel-bandwidth
421 Add 25 kHz Channel Bandwidth Support to NBFM Decoder
2018-09-16 07:22:38 -04:00
Denny
a17132be0d Updates the NBFM decoder to allow user to select between 12.5 and 25.0
kHz channel bandwidth so that 25 kHz channels can be demodulated correctly.
2018-09-16 07:21:14 -04:00
Denny Sheirer
dae68515a5 Merge pull request #420 from DSheirer/419-heterdyne-channelizer-center-frequency-tuning
419 Heterodyne Channelizer Center Frequency Calculations
2018-09-16 06:04:09 -04:00
Denny
0aaec8ff54 Updates the heterodyne channelizer's center frequency calculator to
only update the center frequency when needed and to place the set of
decoding channels adjacent to the right side of the tuner's center
frequency when the complete set of channels fit into the usable half
bandwidth of the tuner.
2018-09-16 06:02:46 -04:00
Denny Sheirer
001883edc2 Merge pull request #418 from DSheirer/417-channel-overflow-zombie-state
417 Traffic Channels in Buffer Overflow state do not tear down correctly
2018-09-16 05:23:05 -04:00
Denny
335fb51456 Updates buffer overflow handling to occur coincident to other channel
states rather than being a channel state itself.  This was preventing
traffic channels that overflow from being torn down correctly.
2018-09-16 05:21:46 -04:00
Denny Sheirer
e94b6ae172 Merge pull request #416 from DSheirer/415-overflow-state
415 Buffer Overflow State Listener Registration
2018-09-14 06:46:34 -04:00
Denny
785125da4f Correctly implements the buffer overflow listener registration so that
decoding channels can be notified when the internal buffer goes into
and out of overflow state.
2018-09-14 06:45:37 -04:00
Denny
81b8f5a423 Updates build to version 0.4.0 alpha 4 2018-09-12 06:23:09 -04:00
Denny Sheirer
bf186a7408 Merge pull request #414 from DSheirer/405-airspy-tuner-waterfall-freeze
405 Airspy Spectrum/Waterfall Freezes
2018-09-12 06:20:36 -04:00
Denny
43fc326f93 Resolves #405
Adds 500 ms delay to initial registering for samples from all tuners to
feed the spectral display/waterfall.  The existing approach of immediately
registering for samples was causing the airspy tuner to lockup
aperiodically.  Investigation via debugger didn't show any application
thread locks ... usb master processor was continuing to service libusb
timeouts.  Unable to debug internal airspy processes.  Adding the delay
to startup seems to be a usable solution.
2018-09-12 06:19:11 -04:00
Denny Sheirer
7cdbb75b42 Merge pull request #413 from DSheirer/375-poor-p25-decode-performance
375 P25 Decode Performance
2018-09-10 04:22:40 -04:00
Denny
96a56cab68 Resolves #375.
Reverts PLL gain values to version 0.3.4beta2 values to restore P25
decoder performance.

This change in conjuction with recent channel sample rate change should
restore P25 channel decoding to comparable levels from 0.3.4b2.
2018-09-09 09:55:40 -04:00
Denny Sheirer
29d4b36cf7 Merge pull request #412 from DSheirer/411-variable-channel-sample-rates
#411 Update Tuner Source Manager to provide variable sample rate channels
2018-09-09 09:50:11 -04:00
Denny
b15a3e59a0 Resolves #411
Updates decoder types to specify channel sample rate and pass/stop
frequencies for requested channels.  Updates tuner channel source
managers to accept a channel specification for channels that are
allocated.
2018-09-09 09:49:05 -04:00
Denny Sheirer
9950e46e5a Merge pull request #408 from DSheirer/406-tuner-sample-delay-buffer
406 Tuner Sample Delay Buffer
2018-09-09 08:47:17 -04:00
Denny
570321da3e Implements a complex sample delay buffer integrated into the tuner
channel manager so that channel source requests can have a start
timestamp specified and the channel can be preloaded with samples from
the delay buffer.  If no timestamp is specified, current time is assumed.

This feature requires corresponding updates to any decoder that wishes
to implement the timestamped sample preloading functionality.
2018-09-09 08:46:21 -04:00
Denny
bb0bcd3198 Updates build to 0.4.0 alpha 3 2018-09-05 07:34:09 -04:00
Denny Sheirer
cb54850f99 Merge pull request #402 from DSheirer/heterodyne_channelizer
401 Update Tuners to use Heterodyne Channelizer
2018-09-05 07:31:57 -04:00
Denny
5bb8c46bd5 Merge remote-tracking branch 'origin/heterodyne_channelizer' into heterodyne_channelizer 2018-09-05 07:27:39 -04:00
Denny
2a3fd2c5a3 Class renaming and cleanup. 2018-09-05 07:27:18 -04:00
Denny
d712feb45a Class renaming and cleanup. 2018-09-05 07:26:52 -04:00
Denny
fbb24a4c05 Class renaming and cleanup. 2018-09-05 07:24:42 -04:00
Denny
3992f8178c Merge remote-tracking branch 'origin/heterodyne_channelizer' into heterodyne_channelizer
# Conflicts:
#	src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
2018-09-05 07:19:37 -04:00
Denny
8642def77e Cleanup, renaming and removing legacy code. 2018-09-05 07:17:51 -04:00
Denny
38e6de6cb2 Resolves cutoff frequency for 2 channel polyphase synthesizer. 2018-09-05 07:17:51 -04:00
Denny
994e78f42b Resolves issue with polyphase filter gain and channel output processor gain 2018-09-05 07:16:51 -04:00
Denny
b8a46af836 Creates new HeterodyneChannelSourceManager that uses frequency translation
and CIC decimation for each channel provided by the tuner.
2018-09-05 07:15:57 -04:00
Denny
1c6d19959a Class renaming and cleanup. 2018-09-05 06:34:13 -04:00
Denny
50bc3088b7 Removes unused legacy code. 2018-09-05 06:31:39 -04:00
Denny
09356848eb Merge branch 'master' into heterodyne_channelizer
# Conflicts:
#	src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
2018-09-05 06:18:27 -04:00
Denny Sheirer
316bfb19c2 Merge pull request #400 from DSheirer/375-channelizer-synthesizer-gain-issue
375 channelizer synthesizer gain issue
2018-09-05 06:17:47 -04:00
Denny
c045c8f16d Updates heterodyne channel manager to correctly lock/unlock the tuner
controller.
2018-09-05 06:11:28 -04:00
Denny
8946d789ff WIP - updated tuners to use CIC decimation tuner channel sources instead
of polyphase channelizer.

Channel sample rate reduced to 30 kHz - seems to work better with P25
decoders than previous 25 kHz.
2018-09-04 08:16:57 -04:00
Denny
19399d9138 Resolves cutoff frequency for 2 channel polyphase synthesizer. 2018-09-01 06:32:24 -04:00
Denny
9acdf22b91 WIP - identifier framework and restructuring P25 messages to provide
identifiers.
2018-08-31 04:50:58 -04:00
Denny Sheirer
f963d821a1 Merge pull request #396 from DSheirer/394-p25-bitstream-output
394 P25 bit stream output
2018-08-18 03:52:36 -04:00
Denny
ade0cef50b Updates P25 C4FM and LSM decoders to provide demodulated bit streams
for recording using the new binary recorders/format.
2018-08-18 03:36:17 -04:00
Denny
2044c2a2de WIP - Updated P25 C4FM and LSM decoders to assemble dibits and push
reusable byte buffers out to the processing chain.  This enables the
processing chain to broadcast these buffers back to the decoder for
message assembly and to any running binary recorder.

Updated P25Decoder to be a reusable byte buffer listener.  The decoder
will now pass reusable byte buffers down to the message framer for
message processing.  This should make the code more efficient since it
can process byte arrays versus handling each individual dibit.

The P25MessageFramer has been updated to be a listener of reusable byte
buffers instead of Dibits, but the actual processing of byte arrays
has to be coded.

Added max recording size to the binary recorder so that it cycles at
500 kB max file size.
2018-08-17 07:27:04 -04:00
Denny Sheirer
1c312a50b6 Merge pull request #395 from DSheirer/393-raw-bitstream-recording
393 Recording raw decoded bitstream from supported decoder types
2018-08-11 08:09:22 -04:00
Denny
54f6bd09e2 Cleanup 2018-08-11 08:08:57 -04:00
Denny
5f8f2ec330 Resolves #393 - Adds support for recording demodulated (raw) bitstream
buffers.  None of the decoders currently support this yet, but the P25
will be updated to provide bitstream output for recording.

Renames the ReusableBuffer class to ReusableFloatBuffer for
consistency.
2018-08-11 08:02:58 -04:00
Denny Sheirer
d7e018f66e Merge pull request #392 from DSheirer/390-polyphase-channel-source-npe
390 PolyphaseChannelSource NPE
2018-08-11 05:43:35 -04:00
Denny
c2a232fdee Adds null checking to output processor. 2018-08-11 05:42:45 -04:00
Denny Sheirer
c5d2dcd0cf Merge pull request #391 from DSheirer/389-add-active-to-channel-states
389 Add ACTIVE Channel State and Update P25 Decoder State processing
2018-08-11 05:39:20 -04:00
Denny
8e4e4129ca Adds 'ACTIVE' as a valid channel state to indicate that the channel is
actively decoding, but no call or data is being transmitted.  This is
primarily used for P25 channels when TDU Link Control messages are
being transmitted in between voice or data transmissions on a channel.

Updated several of the P25 decoder state message processing sections to
keep the channel state UI updated with current call identifiers and
states.

Hard-coded channel state colors to remove inefficiency in the way that
channel state coloration was being handled.
2018-08-11 05:36:30 -04:00
Denny
9176ca610f Updates build to Version 0.4.0 Alpha 2 2018-08-05 07:51:40 -04:00
Denny Sheirer
97b31e4d9b Merge pull request #385 from DSheirer/372-icecast-streaming-reconnect
372 Icecast Streaming Reconnect and Metadata Updates
2018-08-05 07:48:28 -04:00
Denny
a3e5520d3e Resolves #372 - Icecast reconnect following network outage. Updates
the lost connection timeout from default of 60 seconds down to 5 seconds
so that the connection state updates more quickly.  Removed complex
error handling and replaced with a simpler approach to catch an error
and disconnect and then attempt a reconnect.

Added new 'Network Unavailable' error state that detects when the network
connection is unavaialable.

Updated the icecast metadata updater to close the TCP session after
successfully writing metadata to the socket.
2018-08-05 07:44:46 -04:00
Denny
8c456972cc Decreases usable bandwidth of the R820T tuner to 98% so that channels
do not get allocated at the spectral extremes where the signal is
degraded.
2018-08-03 11:02:47 -04:00
Denny
35d81ab31d Decreases buffer size for low sample rates for RTL2832 tuners. 2018-08-03 08:06:00 -04:00
Denny
b90b06e600 Merge remote-tracking branch 'origin/master' 2018-08-03 07:57:58 -04:00
Denny
04144ca677 Adds additional RTL 2832 sample rate options 2018-08-03 07:57:47 -04:00
Denny Sheirer
d3c7a50918 Merge pull request #384 from DSheirer/382-rtl-tuner-sample-rate-options
382 - Additional Sample Rate Options for RTL2832 Tuners
2018-08-03 07:53:01 -04:00
Denny
0fd29fa35a Resolves #382 - Adds additional sample rate options for the RTL2832
tuner and adjusts the low sample rate buffer size down to 16 kb.
2018-08-03 07:50:36 -04:00
Denny Sheirer
c00b173064 Merge pull request #383 from DSheirer/381-rtl-tuner-adaptive-native-buffer-size
381 Adaptive USB Transfer Buffer Sizes for RTL2832 Tuners
2018-08-03 07:29:31 -04:00
Denny
2718367259 Resolves #381. Updates USB transfer processor to use native transfer
buffer sizes that adapt to the selected sample rate.
2018-08-03 07:27:58 -04:00
Denny Sheirer
3f0c13096a Merge pull request #380 from DSheirer/374-rtl-tuner-sample-rate-issue
374 RTL Tuner Sample Rates Not Supported (1.44 & 2.88 MHz)
2018-08-03 07:04:28 -04:00
Denny
71ba4adbd7 Resolves #374
Updates polyphase channel manager to correctly calculate the number of
channels for each sample rate change.
2018-08-03 07:02:58 -04:00
Denny Sheirer
3908e29a2e Merge pull request #376 from DSheirer/373-tuner-ppm-monitoring-and-adjustment
#373 - Tuner PPM Error Monitoring and R820T PPM Auto-Correction
2018-07-29 11:18:47 -04:00
Denny
0c47efea03 Resolves #373 - Adds support for Tuner frequency error PPM monitoring
and auto-correction for R820T tuners.

-Updated TunerModel and TunerViewPanel to add 2 new columns to
display the current tuner's PPM setting and (when available) the
measured frequency error averaged over 5 second intervals provided by
certain decoders (e.g. P25 C4FM and LSM).  Frequency error value from
P25 decoders is provided from the Costas Loop PLL once 2 or more P25
message sync events have been observed in a short period.
-Updated R820T tuner to auto-correct PPM when measured frequency error
exceeds the +/- 0.4PPM threshold for a period of 30 seconds or more.
Feature can be enabled/disabled by user in the tuner editor panel.

Resolved several issues with P25 C4FM/LSM decoders that was impacting
performance related to channel misalignment from tuner PPM offsets.
2018-07-29 10:48:29 -04:00
Denny
5c0d97d942 Resolves issue with array out of bounds exception on shutdown while
removing tuners from the tuner model.
2018-07-20 05:59:00 -04:00
Denny
61ae0d2d83 Resolves issue with saved setting for spectral display panel enabled state 2018-07-20 05:58:23 -04:00
Denny Sheirer
68f2a8da2c Merge pull request #371 from DSheirer/370-tuner-editor-lock-state
Resolved #370 Tuner Editor Lock State
2018-07-15 05:55:46 -04:00
Denny
0d69f11e65 Resolved #370 Tuner Editor Lock State
Resolves an issue with the previous implementation where the tuner
editor was being refreshed repeatedly to update the lock state for the
tuner, rendering the editor almost completely unusable.

Now updates only the frequency control and the sample rate control to
reflect the tuner's lock state.
2018-07-15 05:55:28 -04:00
Denny Sheirer
b3d5daea8b Merge pull request #369 from DSheirer/320-mixer-gain-r820t-tuner
360 R820T Mixer Gain Labels
2018-07-15 05:30:45 -04:00
Denny
fe34623c4a Resolves #320
Changes labels for R820T mixer gain settings.
2018-07-15 05:29:21 -04:00
Denny Sheirer
353747cecc Merge pull request #368 from DSheirer/96-disable-spectral-display-option
#96 Disable Spectral Display Option
2018-07-14 16:05:00 -04:00
Denny
a8c649ef64 Resolves #96
Adds capability to disable spectral display.

Adds right-click context
menu items to disable display and/or reenable display by selecting
another tuner.

Adds view menu items to disable spectral display or show a tuner.

Persists spectral display enabled state across application run sessions.
2018-07-14 16:04:18 -04:00
Denny Sheirer
8cf50e629e Merge pull request #367 from DSheirer/366-application-shutdown
366 Application Shutdown Procedure
2018-07-14 14:45:00 -04:00
Denny
20a8f2bb3c Fully implements application shutdown procedure that is invoked when the
user closes the the main gui window or invokes exit from the file menu.
2018-07-14 14:44:03 -04:00
Denny
8c237be682 Resolves issue with Mute/Unmute icon rendering. 2018-07-14 13:39:43 -04:00
Denny Sheirer
2d5271b849 Merge pull request #365 from DSheirer/364-p25-pll-lock-gain
#364 Excessive P25 PLL Sync Lock Timelines
2018-07-14 11:29:18 -04:00
Denny
609a1e86c7 Resolves #364.
The C4FM and LSM decoders were issuing a PLL reset with each new
channel that was allocated, causing any existing decoding channels to
be disrupted.  Decoding re-sync delays or inability to recapture the
 signal for decoding would likely result as the tuner's overall PPM
 caused misadjustment in the channel.

Resolved a NPE that was occurring in the FrequencyController for
sample rate changes.
2018-07-14 11:27:38 -04:00
Denny Sheirer
821e719015 Merge pull request #363 from DSheirer/170-airspy-mini-sample-rates
#170 Add Support for Airspy Mini tuner's 3 and 6 MHz
2018-07-14 07:47:03 -04:00
Denny
104c4f72be Resolves #170 adding support for Airspy Mini tuner's 3 and 6 MHz
sample rates.

Resolves issue in PolyphaseChannelManager responding to sample rate
changes in the tuner.  The manager now recreates the channelizer when
needed if the tuner's sample rate has changed since the last time the
channelizer was used.

Updates the Tuner selection view to reflect tuner lock state.  Updates
the tuner editors to enable/disable frequency controller and sample
rate controls according to the tuner's lock state.
2018-07-14 07:45:42 -04:00
Denny
9e7fa7618d Logging cleanup. 2018-07-04 06:05:15 -04:00
Denny
ee09520181 Updates build script to version 0.4.0 alpha 1. Adds missing resampler
library.
2018-07-01 20:52:38 -04:00
Denny Sheirer
fa12b840db Merge pull request #357 from DSheirer/39-polyphase-channelizer
39 polyphase channelizer and decoder updates
2018-07-01 07:30:06 -04:00
Denny
183d7d6d19 Logging cleanup 2018-07-01 07:25:37 -04:00
Denny
e2550869ce Disables debug logging of reusable buffer creation. 2018-07-01 07:21:42 -04:00
Denny
e88944e53d Updates all reusable buffer queues to automatically set the user count
to 1 on all provided buffers by default.
2018-07-01 07:08:52 -04:00
Denny
0051a1d381 Removes debug logging for polyphase channelizer and reusable buffer queues. 2018-07-01 06:43:40 -04:00
Denny
f7a873dd0c Removes AFC settings from all decode configurations. 2018-07-01 05:43:42 -04:00
Denny
ad560db5ea [WIP] Removes all usage of Buffer, RealBuffer and ComplexBuffer. 2018-06-30 06:54:36 -04:00
Denny
6f1ce79461 [WIP] Updates the AM decoder module.
Removes a number of unused classes in an effort to remove RealBuffer
usage across the codebase (has been replaced by ReusableBuffers).
2018-06-30 05:37:13 -04:00
Denny
df1bc1031e [WIP] Updates AudioPackets to be ReusableAudioPackets. 2018-06-29 06:53:03 -04:00
Denny
a621a31aa9 [WIP] Updates the sample adapters to natively use ReusableBuffers.
Refactors a MixerReader instance from the RealMixerSource so that it
can be reused for both real and complex data outputs.

Updates RealMixerSource to use refactored MixerReader and use a default
sample rate of 8 kHz.

Updates the ComplexMixer to use the refactored MixerReader.

Updates the FCD 1/2 tuners for compatibility with the new ComplexMixer.

Removes several unused classes.
2018-06-24 09:50:36 -04:00
Denny
19fa256c60 [WIP]Updates mixer manager and mixer source to use 8 kHz audio for
sound card inputs.

Updated DecoderFactory to only include an AM/FM demodulator when we're
using a tuner input.
2018-06-23 09:49:40 -04:00
Denny
7a830077bd [WIP] Cleanup. Refactors DecoderFX application to the gui package. 2018-06-15 07:29:27 -04:00
Denny
f4793f3cfd [WIP] Cleanup. Removes the legacy Instrumentable, Taps, etc and related
gui components.  Refactors the new DecoderFX application to the
instrument package.
2018-06-15 07:27:17 -04:00
Denny
2cd3b73633 [WIP]Updates the MDC1200 decoder to use the new AFSK1200 decoder
class.

Adds Demodulator Viewer FX pane for MDC1200 decoder.
2018-06-15 07:11:07 -04:00
Denny
18f50485d3 [WIP]Updates the Tait1200 decoder to use the new AFSK1200 decoder
class.

Adds Demodulator Viewer FX pane for Tait1200 decoder.
2018-06-15 06:56:44 -04:00
Denny
b410cf0dfe [WIP]Updates the LJ-1200 decoder to use the new AFSK1200 decoder
class.

Adds Demodulator Viewer FX panes for MPT-1327 and LJ-1200 decoders.
2018-06-15 06:19:50 -04:00
Denny
1fdcb623a6 [WIP]Updates the MPT-1327 decoder to use the new AFSK1200 decoder
class.
2018-06-11 04:32:30 -04:00
Denny
9dda1f95a1 [WIP] Creates a new AFSK-1200 correlation decoder and updates the
Fleetsync II decoder to use it.  Adds instrumented version of the decoder
and a new Fleetsync2 FX pane to the DemodulatorViewerFX tool.

Note: the remaining AFSK-1200 based decoders will all be ported to use
this new AFSK1200 decoder (MDC1200, Tait1200, LowJack1200).
2018-06-09 08:17:18 -04:00
Denny
a5c8552937 [WIP] new AFSK1200 decoder. 2018-06-09 04:38:47 -04:00
Denny
e4209c8e4f [WIP] Updates the passport decoder to use the new LTRDecoder.
Deprecates the Listener<Boolean> interface method in the MessageFramer
to optimize for primitive booleans and reduce impact on memory.  Will
remove this interface once the AFSK1200 decoders are updated.
2018-06-02 09:31:49 -04:00
Denny
a898be265f [WIP] Updates the LTR-Standard and LTR-Net decoders to use a new LTR
decoder with a zero-crossing symbol timing error detector.  The new
decoder is optimized for 8 kHz FM demodulated audio produced by the
AudioModule.
2018-06-02 08:47:48 -04:00
Denny
c13315f731 [WIP] The Two-Channel synthesizer is now fully-functional and the
channelizer sub-system is functionally complete.

Updated the channelizer and synthesizer filter design methods to use a
Kaiser windowed Nyquist filter.  Updated the synthesizer filter design
to use a new cutoff frequency of 105% of the channel bandwidth to
resolve a slight dip in the synthesized channel gain at the channel
union region.

Updated the 2 channel output processor to use a low-pass filter that
is designed for the runtime channel sample rate versus hard-coded 12.5
khz.

Testing and redesign continues ...
2018-05-20 07:21:14 -04:00
Denny
341e740e77 [WIP] updated the 2 channel synthesizer to use a more SIMD friendly
filter structure.  Updated the channel results array buffer to produce
reusable buffers.

This has dropped the overall memory usage considerably when profiling.

Resolved issue in the processing chain where polyphase channel sources
were not being stopped and were stacking up in memory.
2018-05-10 08:08:20 -04:00
Denny
cbc228d798 [WIP] The 2 channel synthesizer appears to be working after restructuring.
Check-point commit.  I'm going to optimize the code so that it can
leverage SIMD instructions.
2018-05-06 07:37:24 -04:00
Denny
488b2e02ce [WIP] Work continues on resolving issues with the 2-channel synthesizer
that is currently up/down converting the signal of interest, but the
synthesizer is not correctly combining the original two channels.

Some experimentation with the tap count for the channelizer indicates
that we can cut the tap count in half and still have decent decoding
on the P25 decoder when paired to an aligned channelized output.  The
2-channel synthesized output decodes partially, but needs more work.

Resolved issue with channel gain in the channelizer that was making the
sample values so small that they were registering as zero when converted
from float to 16-bit int value in the complex wave recorder.
2018-04-29 06:19:39 -04:00
Denny
e395d737f8 [WIP]-39 updates to resolve issue with stopping channel streams. 2018-04-22 04:44:35 -04:00
Denny
7a9f705517 Merge branch 'master' into 39-polyphase-channelizer
# Conflicts:
#	src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FM.java
2018-04-18 06:00:57 -04:00
Denny
dfaf506e23 Updates changelog 2018-04-18 05:52:15 -04:00
Denny
81c9a352a8 Updates changelog 2018-04-18 05:51:55 -04:00
Denny
e6937764c3 Merge remote-tracking branch 'origin/master' 2018-04-18 05:49:16 -04:00
Denny
5bfc5bf456 Increments build to 0.3.4 beta 2 2018-04-18 05:49:04 -04:00
Denny Sheirer
ce83273393 Merge pull request #345 from DSheirer/344-p25-c4fm-agc
344 Add AGC to P25 C4FM decoder
2018-04-18 09:48:19 +00:00
Denny
95aa9f8803 Adds automatic gain control to P25 C4FM decoder to condition the
samples prior to interpolation.

Resolves issue with using incorrect quadrature index when pulling a
sample from the interpolating buffer.

Resolves issue where the instrumented version of the interpolating
buffer was being constructed in the non-interpolated decoder component.
2018-04-18 05:47:17 -04:00
Denny
e971ad4582 39 [WIP] - restructured the polyphase filter process. Previous version
was not computationally feasible with my 3rd Gen i7 processor running
and airspy at 10 MHz.  Restructured the polyphase channelizer to use
vector iteration structures that the JDK 9 JIT compiler can translate
into SIMD instructions.  Separated the IFFT processing from the filter
process so that they can be executed on separate threads and this allows
the processing to occur real time.

P25 C4FM decoding at 25 kHz channel sample rate using an airspy
10MHz sample stream seems to be working nicely with ~25% CPU usage and
~300 to 500 MB memory usage.  Incremental (parallel) channel decoding
should have a minimal increase in CPU loading since all of the 12.5 kHz
channels are available in the polyphase channelizer and the only
additional workload is the decoding of the 25 kHz sample stream.

There is still an issue with stopping channels and the channelizer keeps
 processing, and occasionally when starting traffic channels something
 deadlocks and causes out of memory.
2018-04-15 09:05:06 -04:00
Denny
ff9395e4b9 39 [WIP] working copy with new SIMD structures in place. 2018-04-14 07:23:33 -04:00
Denny
d023da9ead 39 [WIP] experiments to reduce processing time for high sample rate tuners (e.g airspy) 2018-03-26 09:47:32 -04:00
Denny
60ffb18cd9 39 [WIP] cleans up the reusable buffers and creates both real and
complex variants.
2018-03-25 09:13:03 -04:00
Denny
c36f6901af 39 - Adds P25/LSM support to the DemodulatorViewerFX application and
adds Feed forward gain support to the C4FM DQPSK demodulator which seems
to improve output from the interpolating sample buffer at each symbol
period.  Updates the symbol timing gain for C4FM to decrease the gain a
bit since the feed forward gain is providing a better interpolated symbol.
2018-03-24 11:03:18 -04:00
Denny
bfaacb309d 39 - WIP - incorporates the latest P25 C4FM decoder from master and
updates the LSM decoder to follow the same design pattern.  Adds LSM
decoder support to the demodulator viewer application.  The current
symbol timing gain for LSM is set too high -- is using the C4FM gain
and causes too much fluctuation in the timing error.
2018-03-24 06:59:41 -04:00
Denny
c3b405a752 Merge branch 'master' into 39-polyphase-channelizer
# Conflicts:
#	src/main/java/io/github/dsheirer/buffer/FloatCircularBuffer.java
#	src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java
#	src/main/java/io/github/dsheirer/dsp/filter/design/FilterView.java
#	src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
#	src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter.java
#	src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java
#	src/main/java/io/github/dsheirer/dsp/mixer/Oscillator.java
#	src/main/java/io/github/dsheirer/dsp/psk/LSMDemodulator.java
#	src/main/java/io/github/dsheirer/module/decode/Decoder.java
#	src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
#	src/main/java/io/github/dsheirer/module/decode/p25/P25Decoder.java
#	src/main/java/io/github/dsheirer/module/decode/p25/P25_C4FMDecoder.java
#	src/main/java/io/github/dsheirer/module/decode/p25/P25_LSMDecoder.java
#	src/main/java/io/github/dsheirer/source/tuner/TunerChannelSource.java
#	src/main/java/io/github/dsheirer/source/tuner/TunerModel.java
#	src/main/java/io/github/dsheirer/source/tuner/TunerSourceEditor.java
#	src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java
2018-03-18 05:19:33 -04:00
Denny
3619b480f8 Updates build to 0.3.4 beta 1.
Note: build 0.3.3 was never promoted beyond beta 3, although there were
no reported issues that would have prevented a 0.3.3 release.
2018-03-17 10:43:24 -04:00
Denny Sheirer
830b2a2d34 Merge pull request #339 from DSheirer/336-audio-recording-temp-files
336 - Audio recording temporary file name
2018-03-17 14:41:14 +00:00
Denny
78bb5ffe43 Resolves #336. Updates audio recorder to use a *.tmp file extension
while the file is open/in-use and change the file extension to *.wav
when the recording is closed.
2018-03-17 10:39:35 -04:00
Denny Sheirer
5dd1664844 Merge pull request #338 from DSheirer/330-p25-c4fm-decoder-update
330 p25 c4fm decoder update
2018-03-17 13:51:19 +00:00
Denny
8868c1f954 330 - updates P25 C4FM decoder to use the new DQPSK demodulator and
removes the previous FM demodulator based decoder.
2018-03-17 09:45:23 -04:00
Denny
646bd78037 330-WIP - adds the message decoder to see the P25 messages being decoded
as we step through the recording.
2018-03-04 20:41:43 -05:00
Denny
5af4cb8706 330-WIP - adds the message decoder to see the P25 messages being decoded
as we step through the recording.
2018-03-04 12:07:29 -05:00
Denny
45a6c5a0b6 330 WIP - implements the new data-directed symbol timing and phase
error calculator.  After increasing the gain for symbol timing, this
new symbol evaluator class seems to track the signal very accurately
despite lots of noise and ISI.
2018-03-04 11:42:23 -05:00
Denny
b563f2d11b 330 WIP - created new symbol evaluator class that combines phase and
timing error calculation with symbol decision.  In the process of
changing over to this new class and remove the previous implementations
for the 3 values.  This commit includes the partial implementation
so I can access from another dev machine.
2018-02-27 05:57:48 -05:00
Denny
148ddd0bd5 330 WIP - P25 C4FM decoder fully functional using DQPSK decoder and
new symbol timing error detector.  Each component now has a subclass
that supports instrumentation for use with the DemodulatorViewerFX
application.
2018-02-25 08:56:34 -05:00
Denny
a0630102e6 330 WIP - P25 C4FM decoder fully functional using DQPSK decoder and
new symbol timing error detector.
2018-02-25 05:04:20 -05:00
Denny
3d1ac30646 330 WIP - changing to 45 degree rotation before demodulation. 2018-02-20 05:27:33 -05:00
Denny
dda37a1198 330 WIP - updates the demodulator viewer with an eye diagram and symbol
chart.  Testing various configurations to see which yields the best
PLL and symbol timing locks.
2018-02-18 16:20:45 -05:00
Denny
eaf7c7d11e 330 WIP on redesigning the P25 C4FM decoder. Creates a new decoder
based on the P25 LSM decoder using a costas loop and a gardner detector.
Updates the Demodulator Viewer FX application to add in a symbol chart
and a phase error chart.  The decoder won't lock on yet ... still a wip.
2018-02-17 13:41:36 -05:00
Denny
80c5ad1bc8 WIP - updates to the demodulator viewer app and the recently used files
menu.
2018-02-13 12:31:50 +00:00
Denny
956bf2d76a Adds phase viewer and recent files menu item to viewer desktop. 2018-02-12 12:34:42 +00:00
Denny
1406d5576a 330 - Adds new JavaFX implementation of the demodulator viewer applicaiton
with initial support for playback control and I/Q sample axis viewing.
2018-02-11 10:16:50 -05:00
Denny
03e39f3343 Merge branch 'master' into 330-p25-c4fm-decoder-update 2018-02-11 06:14:23 -05:00
Denny
28f351287c 328 - cleanup issue with NPE 2018-02-11 06:13:56 -05:00
Denny
54227703a2 Cleanup from last PR. 2018-02-11 06:12:57 -05:00
Denny
bb078de0a9 Cleanup from last PR. 2018-02-10 10:23:28 -05:00
Denny
ec30cd6305 Updates changelog 2018-02-10 08:28:40 -05:00
Denny Sheirer
5a88ac0200 Merge pull request #329 from DSheirer/328-auto-start-ordering
328 Channel Auto-Start capability
2018-02-10 08:15:10 -05:00
Denny
05eea44f7e Cleanup 2018-02-10 08:14:00 -05:00
Denny
030fc1617c 328 Channel Auto-Start capability. Adds auto-start and auto-start
ordering to each channel configuration to provide the user with greater
control over initial application startup configuration.

Adds new dialog window that displays on startup that allows the user to
invoke/cancel auto-start, or simply allow the timer to expire whereby
the channels will be auto-started.

Updates channel table model to include an auto-start column and relabels
the 'Enabled' column to read 'Playing'.

Updates channel editor to change the 'Enable/Disable' button to read
'Start/Stop'.

Changes the 'enabled' property of the channel configuration to indicate
the current 'processing' status - isProcessing().

Channel's XML configuration re-purposes the 'enabled' property to indicate
auto-start and adds a new (optional) auto-start 'order' attribute.
2018-02-10 08:09:43 -05:00
Denny Sheirer
594a77a9d0 Merge pull request #327 from DSheirer/321-channel-preferred-tuner
321 channel preferred tuner
2018-02-09 07:13:13 -05:00
Denny
c2fcc9e85d 321 - adds a preferred tuner option to the channel's source configuration
so that on startup, the application attempts to source the channel from
the preferred tuner.

If the tuner is currently not available, or if the tuner cannot source
the channel, then the next available tuner is selected.
2018-02-09 07:11:38 -05:00
Denny
28da8b2fa4 [wip] reusable buffer support. 2018-02-09 05:56:20 -05:00
Denny
1c06c4382f Adds preferred tuner to tuner source configuration. 2018-02-09 05:55:29 -05:00
Denny
28cb9d9407 [WIP] Updating dsp modules to accept variable sample rate I/Q streams
and creating a new audio resampler.
2018-02-09 05:29:07 -05:00
Denny
96d9d6f750 [WIP] Adds locking mechanism to tuner controllers so that the
polyphase channel manager can block users from changing frequency and/or
sample rate when polyphase channels are allocated.
2018-02-04 06:30:25 -05:00
Denny
85691edacb [WIP] Testing completed for channel manager allocating tuner channels
when the tuner controller indicates that there is a center DC spike
blocked region or when the tuner has clipped outer spectral regions
resulting in less than 100% usable bandwidth.
2018-02-04 04:59:27 -05:00
Denny
f2f2c92afe [WIP] Implements reusable sample buffers across all components. This
produces a significant reduction in memory usage, especially for tuners
like the airspy and hackrf that produce 10/20 MSPS sample rates.  This
is observed to be around 66% reduction in peak memory usage and provides
a more stable memory utilization across time reducing stress on the
garbage collector.

Use of these reusable memory buffers will be pushed further down into
the processing chain modules to ensure we keep new object creation
to the lowest levels possible.
2018-02-03 10:05:16 -05:00
Denny
fcd3c2fd46 [WIP] channelizer and 1/2 channel output processors along with the 2
channel synthesizer ... all appear to be functional.
2018-01-28 10:52:53 -05:00
Denny
e78d2339ff WIP - aligns the SynthesizerViewer configuration with that of the
ChannelizerViewer so that we can compare the two-channel synthesis
operations.  The SynthesizerViewer is successfully down-sampling and
synthesizing two 25.0 kHz channels into a combined 25.0 kHz channel with
no noticeable drop/nulling in the joined region.  However, the channelizer
viewer's synthesized channels definitely have a null in the joined
region.  I suspect that the channelizer is not quite where it needs to
be and am going to start experimenting there.
2018-01-27 16:27:58 -05:00
Denny
673c8a2423 WIP - two-channel output processor is correctly synthesizing a channel
and translating it by -FS/4 to correctly center the synthesized channel.
The low-pass filter is currently disabled to better troubleshoot/view the
state of the synthesized channel.  It appears that the synthesizer is
not correctly joining the channel ... instead of down-sampling and then
synthesizing the two channels, it appears to be synthesizing and then
down-sampling.  The synthesized channel has a null in the middle which
would render it unusable.
2018-01-27 15:57:22 -05:00
Denny
5a03a0038c WIP - Adds FS/4 up and down converter classes. 2018-01-27 15:37:26 -05:00
Denny
e7f7d27a1f Updates the Oscillator and LowPhaseNoiseOscillator classes to share a
common interface and abstract class.
2018-01-27 04:41:01 -05:00
Denny
0cf964fa98 Updates test tuner to cleanup the buffer reuse settings 2018-01-20 04:26:53 -05:00
Denny
b6f282a85a Polyphase Channelizer Viewer is fully functional again, now using the
TestTuner and TestTunerController implementations.  This implementation
only has support for channels that are completely centered within a
channelizer output.  Two output slice stitching to form a composite
channel will be implemented next.

Initial profiling indicates that the FFT function within the polyphase
channelizer is now the dominant processing workload - 28% cpu loading
when channelizing a 10 MHz sample rate input into 800 channels running
at 25.0 kHz each.  This is ideal for offloading to an OpenCL kernel and
running on a GPU.
2018-01-19 12:44:31 -05:00
Denny
32ab0598e8 Updates sample generator and test tuner controller to support multiple
complex buffer listeners.  Removes complex buffer broadcast support from
the source manager and renames it to channel source manager to convey
that it's only responsible for providing channels from the tuner.
2018-01-15 05:34:36 -05:00
Denny
5ae3cb38e1 Merge master and re-apply the source event structures to the DFT processor. 2018-01-15 05:03:03 -05:00
Denny
78205bb1bf Merge branch 'master' into 39-polyphase-channelizer
# Conflicts:
#	src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java
2018-01-15 04:56:16 -05:00
Denny Sheirer
3d9b38bef5 Merge pull request #319 from DSheirer/318-dft-processor-non-blocking-queue
318 DFT Processor Non-Blocking Queue redesign
2018-01-15 04:54:40 -05:00
Denny
d5b377b1af Resolves #318. Redesigns DFT processor to use a non-blocking queue approach to
access incoming buffer streams coupled with a overflowable queue that
limits the quantity of complex buffers that are cached for the DFT
processor.
2018-01-14 12:05:39 -05:00
Denny
a7a0c26b38 #39 WIP - changes for testing of the channelizer viewer. 2018-01-14 08:40:34 -05:00
Denny
d4761be00a #39 WIP - rolls back embedding source events in buffers. The channel
calculator must be updated immediately any time the tuner's center
frequency changes so that a channel can be allocated immediately.  So,
I changed source event handling to two parts where the channel calculator
is immediately updated and the channelizer configuration is deferred so
that it can happen on the buffer processor execution thread and we can
avoid any sync() locking on the channelizer or polyphase channel
sources.
2018-01-13 11:43:26 -05:00
Denny
05fa5c25fd #39 WIP - adds support for timestamped complex buffers so that decoded
messages and events can be accurately timestamped, and more importantly,
allocated traffic channels can request to be pre-loaded with buffered
samples to ensure that traffic channels don't miss any samples that
are currently missed due processing latency inherent in current buffer
sizing.  This should reduce the clipping of starting messages or voice
on the traffic channels.

Restructures processing path for tuner frequency and sample rate change
events.  The tuner now embeds these events in the sequence of buffers
that are generated immediately after the event occurs since those are
the buffers that will be impacted by the change.  Previously, the change
event would be broadcast throughout the system and would impact any
buffered/cached buffers.  This also reduces the number of source event
change listeners that have to be managed.  Note: this change is not yet
implemented in all tuners ... only in the TestTuner implementation.

Updates the polyphase channelizer to now buffer channel results and then
dispatch the buffer to the channel output processors to improve efficiency
of using float arrays versus single value complex float processing.
2018-01-13 09:10:57 -05:00
Denny Sheirer
69e9bed2af Updated readme 2018-01-12 03:50:57 -05:00
Denny
d2e7f008c8 WIP - implements tuner source manager to calculate and update the tuner's
center frequency each time we allocate a new DDC/channel.  Adds
 support for broadcasting channel count change source events so that the
 tuner can accurately convey sourced channel counts via tuner events.

 Completes changeover of polyphase channel manager to support arbitrary
 bandwidth channels.  The new approach provides greater compatibility
 across any tuner sample rates.  Likewise, all decoders will have to be
 updated to adapt to any channel sample rate, although channel bandwidths
 will be at least 12.5 kHz and likely no more than 25.0 kHz, with a 2x
 oversampling that produces channel sample rates in range 25.0 - 50.0
 kHz.
2018-01-07 07:46:41 -05:00
Denny
ddad4b6fb0 Updates readme 2018-01-01 13:16:20 -05:00
Denny
b65dd7e414 Updates readme 2018-01-01 08:20:30 -05:00
Denny
b076587f30 Updates readme 2018-01-01 08:17:42 -05:00
Denny
236df8f3f4 Merges from master branch. 2017-12-27 10:10:51 -05:00
Denny
667c50174d Merge branch 'master' into 39-polyphase-channelizer
# Conflicts:
#	.idea/dictionaries/denny.xml
#	.idea/libraries/imports.xml
#	sdrtrunk.iml
#	src/audio/AudioModule.java
#	src/audio/broadcast/StreamManager.java
#	src/buffer/FloatCircularBuffer.java
#	src/channel/state/AlwaysUnsquelchedDecoderState.java
#	src/channel/state/ChannelState.java
#	src/channel/state/DecoderState.java
#	src/channel/traffic/TrafficChannelManager.java
#	src/controller/channel/Channel.java
#	src/dsp/filter/FilterFactory.java
#	src/dsp/filter/Window.java
#	src/dsp/filter/design/FilterView.java
#	src/dsp/filter/design/FilterViewer.java
#	src/dsp/filter/fir/FIRFilterSpecification.java
#	src/dsp/filter/fir/remez/RemezFIRFilterDesigner.java
#	src/dsp/gain/AutomaticGainControl.java
#	src/dsp/gain/ComplexFeedForwardGainControl.java
#	src/dsp/mixer/Oscillator.java
#	src/dsp/psk/LSMDemodulator.java
#	src/gui/control/JFrequencyControl.java
#	src/instrument/tap/stream/BinaryTapViewPanel.java
#	src/module/Module.java
#	src/module/ProcessingChain.java
#	src/module/decode/Decoder.java
#	src/module/decode/am/AMDecoder.java
#	src/module/decode/fleetsync2/Fleetsync2Decoder.java
#	src/module/decode/fleetsync2/Fleetsync2DecoderState.java
#	src/module/decode/lj1200/LJ1200Decoder.java
#	src/module/decode/lj1200/LJ1200DecoderState.java
#	src/module/decode/ltrnet/LTRNetDecoder.java
#	src/module/decode/ltrnet/LTRNetDecoderState.java
#	src/module/decode/ltrstandard/LTRStandardDecoder.java
#	src/module/decode/ltrstandard/LTRStandardDecoderState.java
#	src/module/decode/mdc1200/MDCDecoder.java
#	src/module/decode/mdc1200/MDCDecoderState.java
#	src/module/decode/mpt1327/MPT1327Decoder.java
#	src/module/decode/mpt1327/MPT1327DecoderState.java
#	src/module/decode/nbfm/NBFMDecoder.java
#	src/module/decode/p25/C4FMSymbolFilter.java
#	src/module/decode/p25/P25Decoder.java
#	src/module/decode/p25/P25DecoderState.java
#	src/module/decode/p25/P25_C4FMDecoder.java
#	src/module/decode/p25/P25_LSMDecoder.java
#	src/module/decode/p25/audio/P25AudioModule.java
#	src/module/decode/passport/PassportDecoder.java
#	src/module/decode/passport/PassportDecoderState.java
#	src/module/decode/tait/Tait1200Decoder.java
#	src/module/decode/tait/Tait1200DecoderState.java
#	src/module/demodulate/am/AMDemodulatorModule.java
#	src/module/demodulate/audio/DemodulatedAudioFilterModule.java
#	src/module/demodulate/fm/FMDemodulatorModule.java
#	src/module/log/EventLogger.java
#	src/record/AudioRecorder.java
#	src/record/RecorderManager.java
#	src/record/wave/ComplexBufferWaveRecorder.java
#	src/record/wave/RealBufferWaveRecorder.java
#	src/sample/Broadcaster.java
#	src/sample/OverflowableTransferQueue.java
#	src/sample/SampleType.java
#	src/source/ComplexSource.java
#	src/source/ISourceEventProvider.java
#	src/source/InvalidFrequencyException.java
#	src/source/RealSource.java
#	src/source/Source.java
#	src/source/config/SourceConfigRecording.java
#	src/source/config/SourceConfigTuner.java
#	src/source/heartbeat/Heartbeat.java
#	src/source/heartbeat/IHeartbeatListener.java
#	src/source/heartbeat/IHeartbeatProvider.java
#	src/source/mixer/RealMixerSource.java
#	src/source/recording/Recording.java
#	src/source/recording/RecordingSourceManager.java
#	src/source/tuner/ITunerChannelProvider.java
#	src/source/tuner/Tuner.java
#	src/source/tuner/TunerClass.java
#	src/source/tuner/TunerController.java
#	src/source/tuner/TunerEditor.java
#	src/source/tuner/TunerManager.java
#	src/source/tuner/TunerModel.java
#	src/source/tuner/TunerSourceEditor.java
#	src/source/tuner/TunerType.java
#	src/source/tuner/airspy/AirspyTuner.java
#	src/source/tuner/airspy/AirspyTunerController.java
#	src/source/tuner/airspy/AirspyTunerEditor.java
#	src/source/tuner/configuration/TunerConfigurationModel.java
#	src/source/tuner/fcd/FCDTuner.java
#	src/source/tuner/fcd/FCDTunerController.java
#	src/source/tuner/fcd/proV1/FCD1TunerController.java
#	src/source/tuner/fcd/proV1/FCD1TunerEditor.java
#	src/source/tuner/fcd/proplusV2/FCD2TunerController.java
#	src/source/tuner/fcd/proplusV2/FCD2TunerEditor.java
#	src/source/tuner/hackrf/HackRFTuner.java
#	src/source/tuner/hackrf/HackRFTunerController.java
#	src/source/tuner/hackrf/HackRFTunerEditor.java
#	src/source/tuner/rtl/RTL2832Tuner.java
#	src/source/tuner/rtl/RTL2832TunerController.java
#	src/source/tuner/rtl/e4k/E4KTunerController.java
#	src/source/tuner/rtl/e4k/E4KTunerEditor.java
#	src/source/tuner/rtl/r820t/R820TTunerController.java
#	src/source/tuner/rtl/r820t/R820TTunerEditor.java
#	src/source/tuner/usb/USBTransferProcessor.java
#	src/source/wave/ComplexWaveSource.java
#	src/source/wave/RealWaveSource.java
#	src/spectrum/ChannelSpectrumPanel.java
#	src/spectrum/DFTProcessor.java
#	src/spectrum/OverlayPanel.java
#	src/spectrum/SpectralDisplayPanel.java
2017-12-26 08:35:33 -05:00
Denny
1c71a37755 #39 WIP - 2017-12-11 20:30:19 -05:00
Denny
318e6f3586 #39 WIP - changes tuner controllers to expose the frequency controller
and the channel manager in preparation for polyphase.
2017-12-11 20:27:01 -05:00
Denny
f38184fd83 #39 WIP - finishes polyphase channel source processing of frequency
offset calculation and updating for channel frequency correction.
2017-12-10 06:26:44 -05:00
Denny
d2085052f6 #39 WIP - updates tunerchannelsource as the parent class for the CIC
and polyphase sub class implementations.  Converts all source implementations
and tuners to treat sample rate as a double.
2017-12-09 15:23:40 -05:00
Denny
441b595325 #39 WIP - initial efforts to restructure the way tuners and tuner
controllers manager sample buffer processing and channel management.
General cleanup across all modules to remove the executor from each
of the start() methods.  Removed the B100 tuner class since it's not
yet implemented.
2017-12-08 04:02:49 -05:00
Denny
57fab2b7ca #39 WIP - initial efforts to restructure the way tuners and tuner
controllers manager sample buffer processing and channel management.
General cleanup across all modules to remove the executor from each
of the start() methods.  Removed the B100 tuner class since it's not
yet implemented.
2017-12-03 11:10:50 -05:00
Denny
bf65e10286 #39 WIP polyphase channelizer
Updates the M2 channelizer and correctly implements the ChannelCalculator
2017-12-02 08:58:34 -05:00
Denny
a12aaf7cf3 #39 WIP polyphase channelizers. 2017-11-18 07:33:28 -05:00
Denny
b3e4bd6a9c #39 WIP polyphase channelizers. 2017-11-18 05:50:00 -05:00
Denny
5281679b7f #39 WIP polyphase channelizers. Creates test tuner and controller to
use in testing tuner and polyphase channel manager operations.
2017-11-12 10:33:13 -05:00
Denny
a581da56a8 #39 WIP polyphase channelizers. 2017-11-05 11:38:01 -05:00
Denny
0181926fe3 WIP - polyphase channelizer 2017-10-30 19:12:14 -04:00
Denny
2c3be02939 WIP - polyphase channelizer 2017-10-23 08:43:53 -04:00
Denny
a00ec42230 Cleanup from master merge 2017-10-22 06:37:24 -04:00
Denny
14a3532030 Merge branch 'master' into 39-polyphase-channelizer
# Conflicts:
#	src/source/tuner/TunerController.java
2017-10-22 06:28:57 -04:00
Denny
3be65d5e9a Resolves issue from merging master branch into polyphase channelizer branch. 2017-10-15 07:26:17 -04:00
Denny
c21f3fca23 Resolves issue from merging master onto branch. 2017-10-15 07:19:36 -04:00
Denny
99d81d247a Merge branch 'master' into 39-polyphase-channelizer
# Conflicts:
#	src/channel/state/ChannelState.java
#	src/module/ProcessingChain.java
#	src/source/Source.java
#	src/source/mixer/RealMixerSource.java
#	src/source/tuner/TunerChannelSource.java
#	src/source/wave/RealWaveSource.java
2017-10-15 07:17:57 -04:00
Denny
27461df7f0 WIP on 2 channel output processor 2017-09-18 04:15:04 -04:00
Denny
9983d2119d WIP 2017-09-10 04:42:35 -04:00
Denny
9e275b7d62 WIP 2017-09-02 06:55:58 -04:00
Denny
f461ed9cdb wip-channelizer 2017-05-13 10:16:18 -04:00
Denny
7c6b7494b8 wip-channelizer 2017-04-21 12:17:55 -04:00
Denny
ab31b40124 Merge branch 'master' into 39-polyphase-channelizer
# Conflicts:
#	src/spectrum/SpectrumPanel.java
2017-04-14 14:42:56 -04:00
Denny
ac686f5f64 WIP - correctly implements the 2x oversampled polyphase channelizer. 2017-04-14 06:41:43 -04:00
Denny
f7cd798f07 WIP 2017-04-08 08:35:48 -04:00
Denny
41d307cdad WIP - checkpointing update for 2x oversampling where we're only computing half of the coefficients. 2017-04-03 19:04:00 -04:00
Denny
afae1654d5 WIP 2017-04-01 15:10:02 -04:00
Denny
ffc92a8f0e WIP - adds remez channelizer builder, working toward combined support (remez & sync) via the filter factory. 2017-03-30 19:16:37 -04:00
Denny
6d9e129087 WIP - adds windowed-sinc (nyquist) filter designer and polyphase channelizer filter designer to FilterFactory. Resolves a few issues with Window designers. ChannelizerViewer is now able to show 800 channels with 12.5 kHz channelization of a 10 MHz bandwidth input signal. 2017-03-26 10:23:38 -04:00
Denny
0e1da8fb3b Updates intellij files and includes missing library 2017-03-23 11:31:15 +00:00
Denny
7d5371db4c Updates build to 0.3.0 Beta 9 2017-03-20 06:12:15 -04:00
Denny
131290fcd2 WIP - experiments 2017-03-11 06:08:23 -05:00
Denny
e2fe06638b Incrementing build to version 0.3.0 beta8 2017-03-05 05:55:04 -05:00
Denny
17c16e16ee Adds a new remez designer that uses BigDecimal to handle the larger values inherent in longer length filters. However, this designer is hugely inefficient and takes forever to calculate the filter. More experimentation needed. 2017-03-02 19:17:22 -05:00
Denny
cd4eaedfcc Merge branch 'master' into 39-polyphase-channelizer 2017-02-26 13:35:33 -05:00
Denny
fd748fec79 [WIP] Using different filters 2017-02-20 20:16:38 -05:00
Denny
14b844f0bd [WIP] Channelizing 6.4 MHz sample rate into 512 channels with with 512-point FFT on each channel at frame rate of 20 fps. 2017-02-19 10:37:44 -05:00
Denny
99ef002d07 [WIP] working on polyphase channelizer and testing viewer 2017-02-19 08:22:37 -05:00
Denny
e868428c88 [WIP] working on polyphase channelizer and testing viewer 2017-02-18 11:05:21 -05:00
Denny
d2542d2560 Merge branch 'master' into 39-polyphase-channelizer
# Conflicts:
#	src/dsp/filter/design/FilterViewer.java
2017-02-18 07:34:18 -05:00
Denny
9ca87299d5 [WIP] Polyphase channelizer experiments 2017-02-06 04:19:09 -05:00
1587 changed files with 153069 additions and 79931 deletions

2
.gitignore vendored
View File

@@ -17,7 +17,7 @@ out/
*.class
*.log
# Package Files #
*.jar
#*.jar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

9
.idea/compiler.xml generated
View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel>
<module name="sdr-trunk_main" target="1.8" />
<module name="sdr-trunk_test" target="1.8" />
</bytecodeTargetLevel>
</component>
</project>

32
.idea/dictionaries/denny.xml generated Normal file
View File

@@ -0,0 +1,32 @@
<component name="ProjectDictionaryState">
<dictionary name="denny">
<words>
<w>bahroo</w>
<w>broadcastify</w>
<w>correlator</w>
<w>dinf</w>
<w>esnid</w>
<w>fleetsync</w>
<w>heterdyned</w>
<w>heterodynes</w>
<w>icecast</w>
<w>ident</w>
<w>nutall</w>
<w>overflowable</w>
<w>polyphase</w>
<w>qpsk</w>
<w>remez</w>
<w>resampler</w>
<w>samplerate</w>
<w>sdrtrunk</w>
<w>sheirer</w>
<w>shoutcast</w>
<w>streamable</w>
<w>talkgroup</w>
<w>tgid</w>
<w>ultravox</w>
<w>wacn</w>
<w>xtea</w>
</words>
</dictionary>
</component>

3
.idea/misc.xml generated
View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="14" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

10
.idea/modules.xml generated
View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk_main.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk_main.iml" group="sdr-trunk" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk_test.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk_test.iml" group="sdr-trunk" />
</modules>
</component>
</project>

2
.idea/vcs.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,3 +1,24 @@
0.4.0 20200404
- Release includes embedded Java 14 & JavaFX 13 runtime
- Adds Polyphase Channelizer with 2x channel oversampling and redesigned source management
- Adds P25 Phase 2 decoder
- Adds P25 radio identifier aliasing
- JMBE library 1.0.4 with support for AMBE tone metadata
- New JavaFX User Preferences dialog
- HackRF tuner enhanced (sample rates and saturation issues)
- Adds support for Airspy mini tuner
- Redesigned audio sub-system that uses AudioSegments
- USB tuner bus saturation management/monitoring
- Bitstream (.bits) recording
- MP3 Audio Recording
- Numerous bug fixes
0.3.4 20180418
- New P25 C4FM decoder that uses Differential QPSK decoder instead of FM demodulation with 4FSK.
- Adds channel auto-start and ordering capability with startup dialog allowing user to invoke or bypass channel
auto-start. Provides user with more control over startup configuration.
- Adds preferred tuner attribute to channel configuration allowing user to select a preferred tuner for the channel
0.3.3 20171225
- Changes build system from ant to gradle. Thanks to: Roman Tsypuk https://github.com/tsypuk
- Adds LIST and ID3 metadata to wav audio recordings

View File

@@ -1,54 +1,66 @@
# sdrtrunk
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted)
* [User's Manual Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_Version0.3.0)
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted_V0.3.0)
* [User's Manual Version 0.3.0 and 0.4.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_V0.3.0)
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
* [Support Group](https://groups.google.com/forum/#!forum/sdrtrunk)
* [Discord Support Channel](https://discord.gg/HJQaKYE)
![sdrtrunk Application Overview - Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/images/ApplicationOverview_V0.3.0.png)
![sdrtrunk Application Overview - Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/v0.3/images/ApplicationOverview_V0.3.0.png)
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
# Instructions:
# End User Instructions:
If you simply want to download and run the program, please follow these instructions.
## Download the latest sdrtrunk release for your operating system
All release versions of sdrtrunk are available from the [releases](https://github.com/DSheirer/sdrtrunk/releases) tab.
* **(alpha)** These versions are under development feature previews and likely to contain bugs and unexpected behavior.
* **(beta)** These versions are currently being tested for bugs and functionality prior to final release.
* **(final)** These versions have been tested and are the current release version.
## Unzip the release
Use 7-zip or any zip utility to unzip the release file
## Start the application
Once unzipped, open a command prompt to where you unzipped the release. Change to the **/bin** directory and use the launch script to start the application:
* **Windows** sdr-trunk.bat
* **Linux/OSX** ./sdr-trunk
## Optional - P25 Audio
If you're using sdrtrunk with a P25 trunked radio system, the [JMBE](https://github.com/DSheirer/sdrtrunk/wiki/JMBE) wiki page contains instructions for downloading the JMBE audio library source code and compiling the JMBE library. Once you have compiled the library, launch the sdrtrunk application. From the menu bar, choose **View >> Preferences**. In the **JMBE Audio Codec** section, update the path to where your compiled JMBE library is located. Any channels that are started after you set the path will be able to produce P25 audio.
## Minimum System Requirements
* **Operating System:** Windows (32 or 64-bit), Linux (32 or 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
* **CPU:** 4-core
* **RAM:** 8GB or more (preferred). Depending on usage, 4GB may be sufficient.
# Developer Instructions:
If you're interested in modifying and/or compiling the source code, please follow these instructions to use gradle to compile the code.
## Build the project
Project uses gradle as build system. You can build it with locally installed gradle or if you do not have/want install gradle you can use preconfigured linux/windows wrapper.
### Build with locally installed gradle 4.3.1
sdrtrunk uses the gradle build system. This requires OpenJDK 11 or higher installed on your local compuber. Use the gradle wrapper to build the source code:
### Linux
```
gradle clean buildSdr
./gradlew clean build
```
### Build with preconfigured wrapper for linux/windows
Linux
### Windows
```
./gradlew clean buildSdr
gradlew.bat clean build
```
Windows
```
gradle.bat clean buildSdr
```
This would add gradle, download all project dependencies and build JAR
The **/build/distributions** folder will contain the zip file of the compiled program. Unzip it and launch the program from the scripts in the **/bin** directory.
## Development
All dependencies/versions are controlled from build.gradle.
To change the new release version tag of artifact - change property:
```
version = '0.3.2'
version = '0.4.0'
```
## Run SDRTrunk
SDRTrunk is packed into single uber-jar file. There is need to have folders with dependecies and
adding them with classpath. Just run with java.
### Use Java8
```
java -jar build/libs/sdr-trunk-all-0.3.2.jar
```
## TODO ITEMS
- [x] Move project to gradle build
- [x] Changed folders structure, based on java project
- [x] Dependencies are moved to gradle and versions are updated
- [x] Create uberJAR with all dependencies
- [ ] Some dependencies left in imports folder. Find them and include into gradle
- [ ] Move images into resources of project
- [ ] Add tests to project. This would guarantee that after refactoring part of functionality is not broken
- [ ] Create modular OOP system (radio-module, gui, web-ui, etc). This would allow to use RADIO modules to call from CLI, GUI or Web UI. And use SDRTrunk as dependency for other projects.

View File

@@ -1,28 +1,103 @@
buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
}
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
plugins {
id 'application'
id 'java'
id 'idea'
id 'org.openjfx.javafxplugin' version '0.0.8'
id 'org.beryx.runtime' version '1.8.0'
}
apply plugin: 'java'
apply plugin: 'idea'
repositories {
jcenter()
mavenCentral()
maven { url "https://jitpack.io" }
}
version = '0.3.3-beta.3'
sourceCompatibility = 1.8
version = '0.4.0'
sourceCompatibility = '13'
sourceSets {
main.java.srcDirs 'src/main'
test.java.srcDirs 'src/test'
}
dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'org.assertj:assertj-core:3.8.0'
//Jitpack imports
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
implementation 'com.github.dnault:libresample4j:master'
implementation 'com.github.DSheirer.jmbe:jmbe-api:1.0.0'
//mavenCentral/jcenter imports
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'ch.qos.logback:logback-core:1.2.3'
implementation 'ch.qos.logback:logback-classic:1.2.3'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8'
implementation 'com.fazecast:jSerialComm:2.5.0'
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
implementation 'com.github.wendykierp:JTransforms:3.1'
implementation 'com.google.guava:guava:27.0.1-jre'
implementation 'com.jidesoft:jide-oss:3.6.18'
implementation 'com.miglayout:miglayout-swing:5.2'
implementation 'com.mpatric:mp3agic:0.9.1'
implementation 'eu.hansolo:charts:1.0.5'
implementation 'io.github.dsheirer:radio-reference-api:15.1.2'
implementation 'javax.usb:usb-api:1.0.2'
implementation 'net.coderazzi:tablefilter-swing:5.4.0'
implementation 'org.apache.commons:commons-lang3:3.8.1'
implementation 'org.apache.commons:commons-io:1.3.2'
implementation 'org.apache.mina:mina-core:2.0.19'
implementation 'org.apache.mina:mina-http:2.0.19'
implementation 'org.controlsfx:controlsfx:11.0.1'
implementation 'org.slf4j:slf4j-api:1.7.25'
implementation 'org.usb4java:libusb4java:1.3.0'
implementation 'org.usb4java:usb4java:1.3.0'
implementation 'org.usb4java:usb4java-javax:1.3.0'
implementation 'pl.edu.icm:JLargeArrays:1.6'
//The must remain as compile options to ensure they are included in the build to produce multi-OS images
compile "org.openjfx:javafx-base:13:win"
compile "org.openjfx:javafx-base:13:linux"
compile "org.openjfx:javafx-base:13:mac"
compile "org.openjfx:javafx-controls:13.0.1:win"
compile "org.openjfx:javafx-controls:13.0.1:linux"
compile "org.openjfx:javafx-controls:13.0.1:mac"
compile "org.openjfx:javafx-graphics:13.0.1:win"
compile "org.openjfx:javafx-graphics:13.0.1:linux"
compile "org.openjfx:javafx-graphics:13.0.1:mac"
compile "org.openjfx:javafx-swing:13.0.1:win"
compile "org.openjfx:javafx-swing:13.0.1:linux"
compile "org.openjfx:javafx-swing:13.0.1:mac"
}
application {
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
}
idea {
module {
downloadJavadoc = true
@@ -30,44 +105,124 @@ idea {
}
}
dependencies {
testCompile 'junit:junit:4.12'
testCompile 'org.assertj:assertj-core:3.8.0'
compile fileTree(dir: 'imports', include: '*.jar')
compile 'org.apache.commons:commons-lang3:3.7'
compile 'org.apache.commons:commons-io:1.3.2'
compile 'org.slf4j:slf4j-api:1.7.25'
compile 'ch.qos.logback:logback-core:1.2.3'
compile 'ch.qos.logback:logback-classic:1.2.3'
compile 'com.miglayout:miglayout-swing:5.0'
compile 'org.usb4java:usb4java-javax:1.2.0'
compile 'org.usb4java:usb4java:1.2.0'
compile 'org.usb4java:libusb4java:1.2.0'
compile 'javax.usb:usb-api:1.0.2'
compile 'com.github.wendykierp:JTransforms:3.1'
compile 'pl.edu.icm:JLargeArrays:1.6'
compile 'net.coderazzi:tablefilter-swing:5.3.1'
compile 'com.jidesoft:jide-oss:3.4.3'
compile 'org.apache.mina:mina-core:2.0.16'
compile 'org.apache.mina:mina-http:2.0.16'
compile 'org.apache.axis:axis-jaxrpc:1.4'
compile 'org.apache.axis:axis:1.4'
compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.3'
javafx {
modules = ['javafx.base', 'javafx.controls', 'javafx.graphics', 'javafx.swing']
}
task wrapper(type: Wrapper) {
gradleVersion = '4.3.1'
}
task buildSdr(type: Jar) {
jar {
manifest {
attributes 'Implementation-Title': 'SdrTrunk project',
'Implementation-Version': version,
'Main-Class': 'io.github.dsheirer.gui.SDRTrunk',
'Class-Path': 'jmbe-0.3.2.jar jmbe-0.3.3.jar'
attributes (
'Implementation-Title' : 'sdrtrunk project',
'Implementation-Version' : archiveVersion,
'Class-Path' : 'jmbe-1.0.4.jar',
'Created-By' : "Gradle ${gradle.gradleVersion}",
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
)
}
baseName = project.name + '-all'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
/**
* Java Development Kit (JDK) locations. In order to build OS-specific images, these paths must point to the 'bin'
* directory within a JDK for each of the specified architectures. These JDKs are used by the runtime and runtimeZip
* tasks to produce platform-specific builds. If none of these paths exists, then an image will be created for the
* host OS using the installed JDK.
*/
def jdk_linux_x86_64 = '/media/denny/WD250GB/java/bellsoft/linux-x64/jdk-14'
def jdk_linux_x86_32 = '/media/denny/WD250GB/java/bellsoft/linux-x86/jdk-14'
def jdk_osx_x86_64 = '/media/denny/WD250GB/java/bellsoft/osx-x64/jdk-14.jdk'
def jdk_windows_x86_64 = '/media/denny/WD250GB/java/bellsoft/windows-x64/jdk-14'
def jdk_windows_x86_32 = '/media/denny/WD250GB/java/bellsoft/windows-x86/jdk-14'
def file_jdk_linux_x86_64 = new File(jdk_linux_x86_64)
def file_jdk_linux_x86_32 = new File(jdk_linux_x86_32)
def file_jdk_osx_x86_64 = new File(jdk_osx_x86_64)
def file_jdk_windows_x86_64 = new File(jdk_windows_x86_64)
def file_jdk_windows_x86_32 = new File(jdk_windows_x86_32)
def hasTargetJdk = file_jdk_linux_x86_32.exists() ||
file_jdk_linux_x86_64.exists() ||
file_jdk_osx_x86_64.exists() ||
file_jdk_windows_x86_32.exists() ||
file_jdk_windows_x86_64.exists()
runtime {
if(file_jdk_linux_x86_64.exists())
{
targetPlatform('linux-x86_64-v' + version, jdk_linux_x86_64)
}
if(file_jdk_linux_x86_32.exists())
{
targetPlatform('linux-x86_32-v' + version, jdk_linux_x86_32)
}
if(file_jdk_osx_x86_64.exists())
{
targetPlatform('osx-x86_64-v' + version, jdk_osx_x86_64)
}
if(file_jdk_windows_x86_64.exists())
{
targetPlatform('windows-x86_64-v' + version, jdk_windows_x86_64)
}
if(file_jdk_windows_x86_32.exists())
{
targetPlatform('windows-x86_32-v' + version, jdk_windows_x86_32)
}
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop', 'java.net.http']
imageZip = hasTargetJdk ? file("$buildDir/image/sdr-trunk.zip") : file("$buildDir/image/sdr-trunk-" + version + ".zip")
}
/**
* Check for existence of JDK folders for each supported platform before creating runtime images
*/
tasks.runtime.doFirst {
if(!file_jdk_linux_x86_64.exists())
{
println("Skipping OS Image - Linux x86 64-bit JDK was not found at " + jdk_linux_x86_64)
}
if(!file_jdk_linux_x86_32.exists())
{
println("Skipping OS Image - Linux x86 32-bit JDK was not found at " + jdk_linux_x86_32)
}
if(!file_jdk_osx_x86_64.exists())
{
println("Skipping OS Image - OSX x86 64-bit JDK was not found at " + jdk_osx_x86_64);
}
if(!file_jdk_windows_x86_64.exists())
{
println("Skipping OS Image - Windows x86 64-bit JDK was not found at " + jdk_windows_x86_64)
}
if(!file_jdk_windows_x86_32.exists())
{
println("Skipping OS Image - Windows x86 32-bit JDK was not found at " + jdk_windows_x86_32)
}
}
/**
* Remove unused JavaFX OS-specific libraries from each platform target directory
*/
tasks.runtime.doLast {
delete(fileTree('build/image/sdr-trunk-linux-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
delete(fileTree('build/image/sdr-trunk-linux-x86_32-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
delete(fileTree('build/image/sdr-trunk-osx-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|linux)\.jar/ })
delete(fileTree('build/image/sdr-trunk-windows-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
delete(fileTree('build/image/sdr-trunk-windows-x86_32-v' + version + '/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
//JavaFX web and media modules are transitive dependencies of ControlsFX but are not required for this application
delete(fileTree('build/image/sdr-trunk-linux-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
delete(fileTree('build/image/sdr-trunk-linux-x86_32-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
delete(fileTree('build/image/sdr-trunk-osx-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
delete(fileTree('build/image/sdr-trunk-windows-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
delete(fileTree('build/image/sdr-trunk-windows-x86_32-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
}

16
copyright.template Normal file
View File

@@ -0,0 +1,16 @@
******************************************************************************
Copyright (C) 2014-${today.year} Dennis Sheirer
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*****************************************************************************

View File

@@ -1,17 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-bin.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

51
gradlew vendored
View File

@@ -1,5 +1,21 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@@ -109,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
@@ -138,19 +154,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
i=`expr $i + 1`
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -159,14 +175,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

18
gradlew.bat vendored
View File

@@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

916
sdr-trunk.ipr Normal file
View File

@@ -0,0 +1,916 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions>
<entry name=".+\.(properties|xml|html|dtd|tld)" />
<entry name=".+\.(gif|png|jpeg|jpg)" />
</resourceExtensions>
<wildcardResourcePatterns>
<entry name="!?*.class" />
<entry name="!?*.scala" />
<entry name="!?*.java" />
<entry name="!?*.groovy" />
</wildcardResourcePatterns>
<bytecodeTargetLevel target="11">
<module name="sdr-trunk_main" target="11" />
<module name="sdr-trunk_test" target="11" />
</bytecodeTargetLevel>
</component>
<component name="CopyrightManager" default="sdrtrunk">
<copyright>
<option name="notice" value="sdr-trunk&#10;Copyright (C) 2014-&amp;#36;today.year Dennis Sheirer&#10;&#10;This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public&#10;License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any&#10;later version.&#10;&#10;This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied&#10;warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.&#10;&#10;You should have received a copy of the GNU General Public License along with this program.&#10;If not, see &lt;http://www.gnu.org/licenses/&gt;&#10;" />
<option name="myName" value="sdrtrunk" />
</copyright>
<LanguageOptions name="JAVA">
<option name="fileTypeOverride" value="3" />
<option name="separateBefore" value="true" />
<option name="separateAfter" value="true" />
</LanguageOptions>
</component>
<component name="DependencyValidationManager">
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</component>
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$USER_HOME$/gradle/gradle-2.12" />
<option name="gradleJvm" value="11" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
<option name="useAutoImport" value="true" />
</GradleProjectSettings>
</option>
</component>
<component name="GradleUISettings">
<setting name="root" />
</component>
<component name="GradleUISettings2">
<setting name="root" />
</component>
<component name="IdProvider" IDEtalkID="11DA1DB66DD62DDA1ED602B7079FE97C" />
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
</profile>
<version value="1.0" />
</component>
<component name="JavadocGenerationManager">
<option name="OTHER_OPTIONS" value="" />
</component>
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
<component name="ProjectDictionaryState">
<dictionary name="Denny">
<words>
<w>baseband</w>
<w>costas</w>
</words>
</dictionary>
</component>
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/sdr-trunk.iml" filepath="$PROJECT_DIR$/sdr-trunk.iml" group="sdr-trunk" />
<module fileurl="file://$PROJECT_DIR$/sdr-trunk_main.iml" filepath="$PROJECT_DIR$/sdr-trunk_main.iml" group="sdr-trunk" />
<module fileurl="file://$PROJECT_DIR$/sdr-trunk_test.iml" filepath="$PROJECT_DIR$/sdr-trunk_test.iml" group="sdr-trunk" />
</modules>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="SvnBranchConfigurationManager">
<option name="mySupportsUserInfoFilter" value="true" />
</component>
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
<component name="libraryTable">
<library name="Gradle: ch.qos.logback:logback-classic:1.2.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.3/7c4f3c474fb2c041d8028740440937705ebb473a/logback-classic-1.2.3.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.3/99b0433b7d86044643b7d677f22d2c793d31d416/logback-classic-1.2.3-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.3/cfd5385e0c5ed1c8a5dce57d86e79cf357153a64/logback-classic-1.2.3-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: ch.qos.logback:logback-core:1.2.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.2.3/864344400c3d4d92dfeb0a305dc87d953677c03c/logback-core-1.2.3.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.2.3/c978d4afd24d32991c8aa31a9c8a1119f19a1cb4/logback-core-1.2.3-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.2.3/3ebabe69eba0196af9ad3a814f723fb720b9101e/logback-core-1.2.3-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.core:jackson-annotations:2.9.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-annotations/2.9.0/7c10d545325e3a6e72e06381afe469fd40eb701/jackson-annotations-2.9.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-annotations/2.9.0/9978adad2c78154ca4aca7cd2a71941e1278f724/jackson-annotations-2.9.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-annotations/2.9.0/a0ad4e203304ccab7e01266fa814115850edb8a9/jackson-annotations-2.9.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.core:jackson-core:2.9.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.8/f5a654e4675769c716e5b387830d19b501ca191/jackson-core-2.9.8.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.8/acc65b3b6e07784dc1760fa4e460238eb2a9073a/jackson-core-2.9.8-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.8/ecaea301e166a0b48f11615864246de739b6619b/jackson-core-2.9.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.core:jackson-databind:2.9.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.8/11283f21cc480aa86c4df7a0a3243ec508372ed2/jackson-databind-2.9.8.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.8/a1dea243882227fc27c350a5b25ca76ef4ecb7d7/jackson-databind-2.9.8-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.8/f66792d499a6fea6c7a743558f940e0ebf775ce3/jackson-databind-2.9.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.8/6a068ddeeafe736da35fba3a7f64462081b562c9/jackson-dataformat-xml-2.9.8.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.8/ba3a439d041b333550de55e34c370d9221011911/jackson-dataformat-xml-2.9.8-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.8/2d48d64536472ff6138cab6ba731b29247fdb0fb/jackson-dataformat-xml-2.9.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.9.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.8/da08815ba1c7f7b435e7df02b7bf98327bad2fd4/jackson-module-jaxb-annotations-2.9.8.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.8/575e1a2a77b830cd3c369931b71b653114314a43/jackson-module-jaxb-annotations-2.9.8-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.8/b68061d95e61cc7c4a574f449cd9db7bad66d75b/jackson-module-jaxb-annotations-2.9.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.woodstox:woodstox-core:5.0.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.woodstox/woodstox-core/5.0.3/10aa199207fda142eff01cd61c69244877d71770/woodstox-core-5.0.3.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.woodstox/woodstox-core/5.0.3/7f5316fa0b4e68daa353bb2df3c546bfd854d54f/woodstox-core-5.0.3-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.woodstox/woodstox-core/5.0.3/f38482e8b060d9e5354c10103f4c4db53c58db7d/woodstox-core-5.0.3-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fazecast:jSerialComm:2.5.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fazecast/jSerialComm/2.5.0/df146deefea578aa38de43a0658f06ac297acb6f/jSerialComm-2.5.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fazecast/jSerialComm/2.5.0/1aa3e4d710396f7bc56661b23a6d125bf5d3749/jSerialComm-2.5.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fazecast/jSerialComm/2.5.0/b9e6222384a17cb10474b7f25768edf4b742ec16/jSerialComm-2.5.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.github.jiconfont:jiconfont-font_awesome:4.7.0.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-font_awesome/4.7.0.1/121d019023f631af04a1357e637dd05a2cc91f7b/jiconfont-font_awesome-4.7.0.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-font_awesome/4.7.0.1/278c25d163a2e2eec5b2e93669b73936365cea69/jiconfont-font_awesome-4.7.0.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-font_awesome/4.7.0.1/8c79f5ccd872310c6dfd4f89f7fe3f748fba94a4/jiconfont-font_awesome-4.7.0.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.github.jiconfont:jiconfont-javafx:1.0.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-javafx/1.0.0/233530f110ab0c3ff845aa52799c43645a11f1c8/jiconfont-javafx-1.0.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-javafx/1.0.0/8cf9282bff0c0cf56cab9ba18f74943019f9a979/jiconfont-javafx-1.0.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-javafx/1.0.0/687e090ac5e1b4d20a242995a80631043d4ee78d/jiconfont-javafx-1.0.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.github.jiconfont:jiconfont-swing:1.0.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-swing/1.0.1/14621054d72340b4a664cce4710ba974a3882a19/jiconfont-swing-1.0.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-swing/1.0.1/55df548424fa5159247b05f02eb712719ce75d5/jiconfont-swing-1.0.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-swing/1.0.1/cae76b75041118062159ffa0a7e2109cc7f88fad/jiconfont-swing-1.0.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.github.jiconfont:jiconfont:1.0.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont/1.0.0/e17d93321975b6735ebc283f6832fa0350614d3a/jiconfont-1.0.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont/1.0.0/8dc32616a97fe4b54380821a6aab9d8930262262/jiconfont-1.0.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont/1.0.0/e79316fba9458423ce944fba6da101262549464/jiconfont-1.0.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.github.wendykierp:JTransforms:3.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.wendykierp/JTransforms/3.1/f1f6d3c9c9bc04f848e38bdb77dea01e8764a01f/JTransforms-3.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.wendykierp/JTransforms/3.1/30208ebb430bee1b2e48e28478853bc9628c0e54/JTransforms-3.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.wendykierp/JTransforms/3.1/fc9cbab6459c03db12a643bc5dc1b1475a3f0b52/JTransforms-3.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.google.code.findbugs:jsr305:3.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.2/25ea2e8b0c338a877313bd4672d3fe056ea78f0d/jsr305-3.0.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.2/7cb5b4e91eb0741882c8fefb2fd0338eff9857c/jsr305-3.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.2/b19b5927c2c25b6c70f093767041e641ae0b1b35/jsr305-3.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.google.errorprone:error_prone_annotations:2.2.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.errorprone/error_prone_annotations/2.2.0/88e3c593e9b3586e1c6177f89267da6fc6986f0c/error_prone_annotations-2.2.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.errorprone/error_prone_annotations/2.2.0/959f406f238a0e047ed2a227479ff9db28173be9/error_prone_annotations-2.2.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.errorprone/error_prone_annotations/2.2.0/a8cd7823aa1dcd2fd6677c0c5988fdde9d1fb0a3/error_prone_annotations-2.2.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.google.guava:failureaccess:1.0.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0.1/1dcf1de382a0bf95a3d8b0849546c88bac1292c9/failureaccess-1.0.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0.1/a9aadb2d577ff54ba3115ee7357cbe65bd2a470f/failureaccess-1.0.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0.1/1d064e61aad6c51cc77f9b59dc2cccc78e792f5a/failureaccess-1.0.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.google.guava:guava:27.0.1-jre">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0.1-jre/bd41a290787b5301e63929676d792c507bbc00ae/guava-27.0.1-jre.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0.1-jre/7515204e130bb73fea9f6df11e58559e6be2dcf0/guava-27.0.1-jre-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0.1-jre/cb5c1119df8d41a428013289b193eba3ccaf5f60/guava-27.0.1-jre-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/b421526c5f297295adef1c886e5246c39d4ac629/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
<library name="Gradle: com.google.j2objc:j2objc-annotations:1.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/976d8d30bebc251db406f2bdb3eb01962b5685b3/j2objc-annotations-1.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/2e1e0c8db5be95ec85d19d456186f8989ea263fa/j2objc-annotations-1.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/dcd31de68a90e41e336f6b7afce0d39285c433d7/j2objc-annotations-1.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.jidesoft:jide-oss:3.6.18">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.jidesoft/jide-oss/3.6.18/2a0b8ef9ed1f18607b70f1e627d862fb8e595373/jide-oss-3.6.18.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.jidesoft/jide-oss/3.6.18/814e47763c22729f665a4232da421824831ea734/jide-oss-3.6.18-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.jidesoft/jide-oss/3.6.18/b9cadcf869c0bdd032756228315252516856ad56/jide-oss-3.6.18-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.miglayout:miglayout-core:5.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.2/c1234205bde8fa72a420ecc5f63b9f269edccbfb/miglayout-core-5.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.2/43595a2a4d2da858dfe1163f0d0ce769b7fbb11/miglayout-core-5.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.2/4b3f0bfa2312173be31f87caa3cc58b617eea1df/miglayout-core-5.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.miglayout:miglayout-swing:5.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.2/53e2fc5586a120797e3af0ff4cf9adb3b403ab14/miglayout-swing-5.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.2/9ca06755a629747815fa91b3cd480bfb40fbb0e1/miglayout-swing-5.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.2/df798d1660b3106a0304efa9e04dfd1a1a2e6025/miglayout-swing-5.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: commons-io:commons-io:1.3.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/commons-io/commons-io/1.3.2/b6dde38349ba9bb5e6ea6320531eae969985dae5/commons-io-1.3.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/commons-io/commons-io/1.3.2/6927a72f39651d8d57070a35c3ba669c2314a47c/commons-io-1.3.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/commons-io/commons-io/1.3.2/b41a5f39fa90007a6c247175933555ba5f166d70/commons-io-1.3.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: eu.hansolo:charts:1.0.5">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/eu.hansolo/charts/1.0.5/2bb9d233ac3e5f678920ff04effd57c0ba21d860/charts-1.0.5.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/eu.hansolo/charts/1.0.5/d65ed2f94c0d5579cf87eea01c5d14fa8196d235/charts-1.0.5-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/eu.hansolo/charts/1.0.5/aa26011a2c525ddf86cb92ed23558b18c0174fa4/charts-1.0.5-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: io.github.dsheirer:radio-reference-api:15.0.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.0.1/8af16506c26025517f0bc971ba35485e96130c9a/radio-reference-api-15.0.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.0.1/219528429b3ed73f285a8c2c4ded40d8eae61fc0/radio-reference-api-15.0.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.0.1/b699a9821030b54fbb7f472f97fcc634afb114fb/radio-reference-api-15.0.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: javax.usb:usb-api:1.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/javax.usb/usb-api/1.0.2/f0ae0d322a99811cf79d508d37f3dc2d82753070/usb-api-1.0.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/javax.usb/usb-api/1.0.2/c0bc03446978d12d8b5b3e8a136a181936f61015/usb-api-1.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/javax.usb/usb-api/1.0.2/4b6337e574535ac803bbc05f3f22808d8bdba0d0/usb-api-1.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: junit:junit:4.12">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.12/2973d150c0dc1fefe998f834810d68f278ea58ec/junit-4.12.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.12/941a8be4506c65f0a9001c08812fb7da1e505e21/junit-4.12-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.12/a6c32b40bf3d76eca54e3c601e5d1470c86fcdfa/junit-4.12-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: net.coderazzi:tablefilter-swing:5.4.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.4.0/ba7415b409712bf1988b2f03eda7730deba26c10/tablefilter-swing-5.4.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.4.0/3bc5c263fc43180f4137a41bd0848851830761ff/tablefilter-swing-5.4.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.4.0/298121afccdf36281e234aaf478f97ddbe8aecf3/tablefilter-swing-5.4.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apache.commons:commons-lang3:3.8.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.8.1/6505a72a097d9270f7a9e7bf42c4238283247755/commons-lang3-3.8.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.8.1/e7e19f37a0e712862967e39558b888fb59ee5b6a/commons-lang3-3.8.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.8.1/e9748d7783594da3735ad9724390a10e7cebc01d/commons-lang3-3.8.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apache.commons:commons-math3:3.5">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.5/13af68e9a11576953f5c4b09436f8207be9d3a27/commons-math3-3.5.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.5/e6106a9927c5ee66d0e647ad116af26c9b6b5f06/commons-math3-3.5-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.5/1d3f412d28c8bff0e8f048240b08bcd21cfa432e/commons-math3-3.5-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apache.mina:mina-core:2.0.19">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.19/b6ba5d8c6d1dd38a1151c590018e4e5edd6c4cfa/mina-core-2.0.19.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.19/b50fe4a0802588c70fa9f23ae6dcefe54a688bca/mina-core-2.0.19-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.19/a070a03134f7c4f5c9ad79b094423d48f3e9551b/mina-core-2.0.19-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apache.mina:mina-http:2.0.19">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.19/b42328dbaeed685376821fe704f70ad36f4de24/mina-http-2.0.19.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.19/3d6ce6d25fc14d858486f50a96213872ca7ae467/mina-http-2.0.19-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.19/ce16dff76ec41be9f9c21df8bb8c271a3bb709cb/mina-http-2.0.19-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.assertj:assertj-core:3.8.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.assertj/assertj-core/3.8.0/b209d90ff2e279bee3e02547ee7b11349c52d0e3/assertj-core-3.8.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.assertj/assertj-core/3.8.0/a2958ce39a3fd0ccdb52502e3a839edec50b6a98/assertj-core-3.8.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.assertj/assertj-core/3.8.0/85dab3e124fb391b10196df9bfdb43640deabb09/assertj-core-3.8.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.checkerframework:checker-qual:2.5.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.checkerframework/checker-qual/2.5.2/cea74543d5904a30861a61b4643a5f2bb372efc4/checker-qual-2.5.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.checkerframework/checker-qual/2.5.2/bfab5d13a888ce75ba0104fdb310808e445ff67d/checker-qual-2.5.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.checkerframework/checker-qual/2.5.2/ebb8ebccd42218434674f3e1d9022c13df1c19f8/checker-qual-2.5.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.codehaus.mojo:animal-sniffer-annotations:1.17">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.codehaus.mojo/animal-sniffer-annotations/1.17/f97ce6decaea32b36101e37979f8b647f00681fb/animal-sniffer-annotations-1.17.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.codehaus.mojo/animal-sniffer-annotations/1.17/c67d33bf1588c89f633cf6b5a629d0e1dc1b02c4/animal-sniffer-annotations-1.17-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.codehaus.mojo/animal-sniffer-annotations/1.17/8fb5b5ad9c9723951b9fccaba5bb657fa6064868/animal-sniffer-annotations-1.17-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.codehaus.woodstox:stax2-api:3.1.4">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.codehaus.woodstox/stax2-api/3.1.4/ac19014b1e6a7c08aad07fe114af792676b685b7/stax2-api-3.1.4.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.codehaus.woodstox/stax2-api/3.1.4/819aa74f907d379c6afc2e0376808ca9feadcbb1/stax2-api-3.1.4-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.codehaus.woodstox/stax2-api/3.1.4/b40604d61c01cc081a4a7bdfc562aa9847ee8857/stax2-api-3.1.4-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.hamcrest:hamcrest-core:1.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/42a25dc3219429f0e5d060061f71acb49bf010a0/hamcrest-core-1.3.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/ad09811315f1d4f5756986575b0ea16b99cd686f/hamcrest-core-1.3-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/1dc37250fbc78e23a65a67fbbaf71d2e9cbc3c0b/hamcrest-core-1.3-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-base:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/7fb2e4a8528ec9e434a9ac9ee98b39af79e6dcb8/javafx-base-11.0.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/f0dfede8adbd1b28d1b5084bd2812ace69ebe673/javafx-base-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/c082cdf645f9833408dcd960bf68344c9384f569/javafx-base-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-base:linux:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/8db178adb1085d455e5ef643a48c34959c8771c1/javafx-base-11.0.2-linux.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/f0dfede8adbd1b28d1b5084bd2812ace69ebe673/javafx-base-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/c082cdf645f9833408dcd960bf68344c9384f569/javafx-base-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-base:mac:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/b774922e5da6a56afee221d1a288d179a1342aa9/javafx-base-11.0.2-mac.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/f0dfede8adbd1b28d1b5084bd2812ace69ebe673/javafx-base-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/c082cdf645f9833408dcd960bf68344c9384f569/javafx-base-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-base:win:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/1852e57b8cf9a9b6488c33605bccd5d06ff210e1/javafx-base-11.0.2-win.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/f0dfede8adbd1b28d1b5084bd2812ace69ebe673/javafx-base-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/c082cdf645f9833408dcd960bf68344c9384f569/javafx-base-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-controls:linux:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/5f6929050a744aab39ebfc1e8e5dd03bcd2ad47b/javafx-controls-11.0.2-linux.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/baa088249ee757119688e78d9c3a256928189902/javafx-controls-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/1535a8b57f01a712405a739a0d69e09ab99dbb6b/javafx-controls-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-controls:mac:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/996371d2868035845572cdb19179dca2bfd1139f/javafx-controls-11.0.2-mac.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/baa088249ee757119688e78d9c3a256928189902/javafx-controls-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/1535a8b57f01a712405a739a0d69e09ab99dbb6b/javafx-controls-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-controls:win:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/6c763707769c18adce406904c771c2ad1fcc370b/javafx-controls-11.0.2-win.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/baa088249ee757119688e78d9c3a256928189902/javafx-controls-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/1535a8b57f01a712405a739a0d69e09ab99dbb6b/javafx-controls-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-graphics:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/e522eb4ea422eceeee207b1c266ba3db19b2343a/javafx-graphics-11.0.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/b7ae1857e54ed7d8ca0e30e01d12fae2b75ba400/javafx-graphics-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/15eadef01f327a851a6d3af0ac89d56292076b13/javafx-graphics-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-graphics:linux:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/ef4c34f0ca77cd99100b76c2ccf1dce383572bb1/javafx-graphics-11.0.2-linux.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/b7ae1857e54ed7d8ca0e30e01d12fae2b75ba400/javafx-graphics-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/15eadef01f327a851a6d3af0ac89d56292076b13/javafx-graphics-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-graphics:mac:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/c1d99ae5bc48e7173f4514a941d27d8f8c972c71/javafx-graphics-11.0.2-mac.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/b7ae1857e54ed7d8ca0e30e01d12fae2b75ba400/javafx-graphics-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/15eadef01f327a851a6d3af0ac89d56292076b13/javafx-graphics-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-graphics:win:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/20459ea2cf714942bcbeb78a7f70ba3531dc1a44/javafx-graphics-11.0.2-win.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/b7ae1857e54ed7d8ca0e30e01d12fae2b75ba400/javafx-graphics-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/15eadef01f327a851a6d3af0ac89d56292076b13/javafx-graphics-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-swing:linux:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/cb57db3e5bd1e2b62b17c8a504858bfc26fa54b1/javafx-swing-11.0.2-linux.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/336999871b9a7e88179a76e2251c73f47e3d5cb6/javafx-swing-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/bcc8c0a402ff01a89a50888c9dc26824a9f3f2cc/javafx-swing-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-swing:mac:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/47efd914c1ebdeda8789a779c8341266340c743/javafx-swing-11.0.2-mac.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/336999871b9a7e88179a76e2251c73f47e3d5cb6/javafx-swing-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/bcc8c0a402ff01a89a50888c9dc26824a9f3f2cc/javafx-swing-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-swing:win:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/9b5728e61148142fae8c47f6295c84d7a8d6c89/javafx-swing-11.0.2-win.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/336999871b9a7e88179a76e2251c73f47e3d5cb6/javafx-swing-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/bcc8c0a402ff01a89a50888c9dc26824a9f3f2cc/javafx-swing-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.slf4j:slf4j-api:1.7.25">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.25/da76ca59f6a57ee3102f8f9bd9cee742973efa8a/slf4j-api-1.7.25.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.25/f003e89ddebdbaca5cf3fc7cc0c67c88c2ca1a3c/slf4j-api-1.7.25-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.25/962153db4a9ea71b79d047dfd1b2a0d80d8f4739/slf4j-api-1.7.25-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.usb4java:libusb4java:darwin-x86-64:1.3.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.usb4java/libusb4java/1.3.0/1fdaa8ec7cfad158be58fbc8e2867d05f2b5fe63/libusb4java-1.3.0-darwin-x86-64.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
<library name="Gradle: org.usb4java:libusb4java:linux-aarch64:1.3.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.usb4java/libusb4java/1.3.0/bb5924dfb86c27ca5d91ccc975c11da2caa3c82d/libusb4java-1.3.0-linux-aarch64.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
<library name="Gradle: org.usb4java:libusb4java:linux-arm:1.3.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.usb4java/libusb4java/1.3.0/504de50cc979b1f19b76ba1154d298632ff1be4e/libusb4java-1.3.0-linux-arm.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
<library name="Gradle: org.usb4java:libusb4java:linux-x86-64:1.3.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.usb4java/libusb4java/1.3.0/9514c818fc97f831277873f082dcefd5394045e4/libusb4java-1.3.0-linux-x86-64.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
<library name="Gradle: org.usb4java:libusb4java:linux-x86:1.3.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.usb4java/libusb4java/1.3.0/7abf7240bcd7c26cb1ea5265eec5e6d703b71b4e/libusb4java-1.3.0-linux-x86.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
<library name="Gradle: org.usb4java:libusb4java:win32-x86-64:1.3.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.usb4java/libusb4java/1.3.0/8cd521a203404d65f4bc59c2a8a38eb4dcd302da/libusb4java-1.3.0-win32-x86-64.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
<library name="Gradle: org.usb4java:libusb4java:win32-x86:1.3.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.usb4java/libusb4java/1.3.0/9cb97c4b8317d8dae0401f80438fde5bfd910b2c/libusb4java-1.3.0-win32-x86.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
<library name="Gradle: org.usb4java:usb4java-javax:1.3.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.usb4java/usb4java-javax/1.3.0/b68f4617513a61e889fbf81926daba9c9accc96d/usb4java-javax-1.3.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.usb4java/usb4java-javax/1.3.0/76268e0268e00cace559a5d3284b065d64044a76/usb4java-javax-1.3.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.usb4java/usb4java-javax/1.3.0/684ff71e8ca72e5536116390909dd37fcb590690/usb4java-javax-1.3.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.usb4java:usb4java:1.3.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.usb4java/usb4java/1.3.0/2d5487fdf303642d480b199a9da0c5a0d2bb0e82/usb4java-1.3.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.usb4java/usb4java/1.3.0/e688082c68718dfbd6f566beb2eebf31cc225036/usb4java-1.3.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.usb4java/usb4java/1.3.0/e6f73cf21dbbc0ecf85417f311e13d8eb278d14a/usb4java-1.3.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: pl.edu.icm:JLargeArrays:1.6">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/pl.edu.icm/JLargeArrays/1.6/be888269f87ebf21f49d979925a07551f6e795ff/JLargeArrays-1.6.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/pl.edu.icm/JLargeArrays/1.6/80bc6536d3d1183ed46bf8a3fccfe8da9a65d34e/JLargeArrays-1.6-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/pl.edu.icm/JLargeArrays/1.6/20a1c4e9de9d8d4943ecdf723bed6a7f843ac20e/JLargeArrays-1.6-sources.jar!/" />
</SOURCES>
</library>
</component>
<component name="masterDetails">
<states>
<state key="ArtifactsStructureConfigurable.UI">
<UIState>
<splitter-proportions>
<SplitterProportionsDataImpl />
</splitter-proportions>
<settings />
</UIState>
</state>
<state key="Copyright.UI">
<UIState>
<splitter-proportions>
<SplitterProportionsDataImpl />
</splitter-proportions>
</UIState>
</state>
<state key="ProjectJDKs.UI">
<UIState>
<splitter-proportions>
<SplitterProportionsDataImpl>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</SplitterProportionsDataImpl>
</splitter-proportions>
<last-edited>1.6</last-edited>
</UIState>
</state>
<state key="ScopeChooserConfigurable.UI">
<UIState>
<splitter-proportions>
<SplitterProportionsDataImpl />
</splitter-proportions>
<settings />
</UIState>
</state>
</states>
</component>
</project>

View File

@@ -1,20 +1,22 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -24,10 +26,10 @@ import io.github.dsheirer.alias.action.AliasAction;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.nonrecordable.NonRecordable;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.record.Record;
import java.awt.*;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -187,7 +189,7 @@ public class Alias
* Returns the priority level of this alias, if defined, or the default priority
*/
@JsonIgnore
public int getCallPriority()
public int getPlaybackPriority()
{
for(AliasID id : mAliasIDs)
{
@@ -245,27 +247,39 @@ public class Alias
{
for(AliasID id : getId())
{
if(id.getType() == AliasIDType.NON_RECORDABLE)
if(id.getType() == AliasIDType.RECORD)
{
return false;
return true;
}
}
return true;
return false;
}
/**
* Sets or removes the non-recordable alias ID for this alias.
* Sets or removes the record alias ID for this alias.
*/
public void setRecordable(boolean recordable)
{
if(recordable)
{
for(AliasID id : getId())
{
if(id.getType() == AliasIDType.RECORD)
{
return;
}
}
addAliasID(new Record());
}
else
{
AliasID toRemove = null;
for(AliasID id : getId())
{
if(id.getType() == AliasIDType.NON_RECORDABLE)
if(id.getType() == AliasIDType.RECORD)
{
toRemove = id;
break;
@@ -277,18 +291,6 @@ public class Alias
removeAliasID(toRemove);
}
}
else
{
for(AliasID id : getId())
{
if(id.getType() == AliasIDType.NON_RECORDABLE)
{
return;
}
}
addAliasID(new NonRecordable());
}
}
/**

View File

@@ -1,34 +1,67 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias;
import com.jidesoft.swing.JideSplitPane;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import net.coderazzi.filters.gui.AutoChoices;
import net.coderazzi.filters.gui.TableFilterHeader;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
public class AliasController extends JPanel
implements ActionListener, ListSelectionListener
public class AliasController extends JPanel implements ActionListener, ListSelectionListener
{
private static final long serialVersionUID = 1L;
private static final String TABLE_PREFERENCE_KEY = "alias.controller";
private AliasModel mAliasModel;
private JTable mAliasTable;
private TableFilterHeader mTableFilterHeader;
private AliasEditor mAliasEditor;
private MultipleAliasEditor mMultipleAliasEditor;
private JideSplitPane mSplitPane;
private UserPreferences mUserPreferences;
private static final String NEW_ALIAS = "New";
private static final String COPY_ALIAS = "Copy";
@@ -39,15 +72,16 @@ public class AliasController extends JPanel
private JButton mDeleteButton = new JButton(DELETE_ALIAS);
private IconCellRenderer mIconCellRenderer;
private JTableColumnWidthMonitor mColumnWidthMonitor;
public AliasController(AliasModel aliasModel, BroadcastModel broadcastModel, IconManager iconManager)
public AliasController(AliasModel aliasModel, BroadcastModel broadcastModel, IconManager iconManager,
UserPreferences userPreferences)
{
mAliasModel = aliasModel;
mAliasEditor = new AliasEditor(mAliasModel, broadcastModel, iconManager);
mMultipleAliasEditor = new MultipleAliasEditor(mAliasModel, broadcastModel, iconManager);
mIconCellRenderer = new IconCellRenderer(iconManager);
mUserPreferences = userPreferences;
init();
@@ -56,9 +90,7 @@ public class AliasController extends JPanel
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0",
"[grow,fill]",
"[grow,fill]"));
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
//System Configuration View and Editor
mAliasTable = new JTable(mAliasModel);
@@ -66,11 +98,11 @@ public class AliasController extends JPanel
mAliasTable.getSelectionModel().addListSelectionListener(this);
mAliasTable.setAutoCreateRowSorter(true);
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_COLOR)
.setCellRenderer(new ColorCellRenderer());
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_COLOR).setCellRenderer(new ColorCellRenderer());
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_ICON)
.setCellRenderer(mIconCellRenderer);
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_ICON).setCellRenderer(mIconCellRenderer);
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mAliasTable, TABLE_PREFERENCE_KEY);
mTableFilterHeader = new TableFilterHeader(mAliasTable, AutoChoices.ENABLED);
mTableFilterHeader.setFilterOnUpdates(true);
@@ -79,8 +111,8 @@ public class AliasController extends JPanel
JPanel buttonsPanel = new JPanel();
buttonsPanel.setLayout(
new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill]", "[]"));
buttonsPanel.setLayout(new MigLayout("insets 0 0 0 0",
"[grow,fill][grow,fill][grow,fill]", "[]"));
mNewButton.addActionListener(this);
mNewButton.setToolTipText("Adds a new alias");
@@ -98,8 +130,8 @@ public class AliasController extends JPanel
JPanel listAndButtonsPanel = new JPanel();
listAndButtonsPanel.setLayout(
new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill][]"));
listAndButtonsPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]",
"[grow,fill][]"));
listAndButtonsPanel.add(tableScroller, "wrap");
listAndButtonsPanel.add(buttonsPanel);
@@ -212,18 +244,11 @@ public class AliasController extends JPanel
*/
private void addAlias(Alias alias)
{
//HACK: when inserting a row to the model, the JTable gets
//notified and attempts to tell the coderazzi table filter
//adaptive choices filter to refresh before the table filter is
//notified of the row additions, causing an index out of bounds
//exception. We turn off adaptive choices temporarily, add the
//channel, and turn on adaptive choices again.
mTableFilterHeader.setAdaptiveChoices(false);
//Remove any column headers so that the newly added alias shows correctly.
mTableFilterHeader.resetFilter();
int index = mAliasModel.addAlias(alias);
mTableFilterHeader.setAdaptiveChoices(true);
if(index >= 0)
{
int translatedIndex = mAliasTable.convertRowIndexToView(index);

View File

@@ -31,8 +31,7 @@ import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class AliasEditor extends Editor<Alias>
implements ActionListener, Listener<AliasEvent>
public class AliasEditor extends Editor<Alias> implements ActionListener, Listener<AliasEvent>
{
private static final long serialVersionUID = 1L;

View File

@@ -1,20 +1,24 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.alias;
import io.github.dsheirer.alias.action.AliasAction;
@@ -31,274 +35,310 @@ import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannelEditor;
import io.github.dsheirer.alias.id.esn.ESNEditor;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.fleetsync.FleetsyncIDEditor;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncIDEditor;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200IDEditor;
import io.github.dsheirer.alias.id.legacy.mobileID.MINEditor;
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327IDEditor;
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordableEditor;
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
import io.github.dsheirer.alias.id.legacy.siteID.SiteIDEditor;
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupIDEditor;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueIDEditor;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.lojack.LoJackIDEditor;
import io.github.dsheirer.alias.id.mdc.MDC1200ID;
import io.github.dsheirer.alias.id.mdc.MDC1200IDEditor;
import io.github.dsheirer.alias.id.mobileID.MINEditor;
import io.github.dsheirer.alias.id.mobileID.Min;
import io.github.dsheirer.alias.id.mpt1327.MPT1327ID;
import io.github.dsheirer.alias.id.mpt1327.MPT1327IDEditor;
import io.github.dsheirer.alias.id.nonrecordable.NonRecordable;
import io.github.dsheirer.alias.id.nonrecordable.NonRecordableEditor;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.priority.PriorityEditor;
import io.github.dsheirer.alias.id.siteID.SiteID;
import io.github.dsheirer.alias.id.siteID.SiteIDEditor;
import io.github.dsheirer.alias.id.radio.Radio;
import io.github.dsheirer.alias.id.radio.RadioEditor;
import io.github.dsheirer.alias.id.radio.RadioRange;
import io.github.dsheirer.alias.id.radio.RadioRangeEditor;
import io.github.dsheirer.alias.id.record.Record;
import io.github.dsheirer.alias.id.record.RecordEditor;
import io.github.dsheirer.alias.id.status.StatusID;
import io.github.dsheirer.alias.id.status.StatusIDEditor;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupID;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupIDEditor;
import io.github.dsheirer.alias.id.uniqueID.UniqueID;
import io.github.dsheirer.alias.id.uniqueID.UniqueIDEditor;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupEditor;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRangeEditor;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.gui.editor.EmptyEditor;
public class AliasFactory
{
public static AliasID copyOf( AliasID id )
{
switch( id.getType() )
{
case BROADCAST_CHANNEL:
BroadcastChannel originalBroadcast = (BroadcastChannel)id;
BroadcastChannel copyBroadcast = new BroadcastChannel();
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
return copyBroadcast;
case ESN:
Esn originalESN = (Esn)id;
Esn copyESN = new Esn();
copyESN.setEsn( originalESN.getEsn() );
return copyESN;
case FLEETSYNC:
FleetsyncID originalFleetsyncID = (FleetsyncID)id;
FleetsyncID copyFleetsyncID = new FleetsyncID();
copyFleetsyncID.setIdent( originalFleetsyncID.getIdent() );
return copyFleetsyncID;
case LTR_NET_UID:
UniqueID originalUniqueID = (UniqueID)id;
UniqueID copyUniqueID = new UniqueID();
copyUniqueID.setUid( originalUniqueID.getUid() );
return copyUniqueID;
case LOJACK:
LoJackFunctionAndID originalLoJackFunctionAndID = (LoJackFunctionAndID)id;
LoJackFunctionAndID copyLoJackFunctionAndID = new LoJackFunctionAndID();
copyLoJackFunctionAndID.setFunction( originalLoJackFunctionAndID.getFunction() );
copyLoJackFunctionAndID.setID( originalLoJackFunctionAndID.getID() );
return copyLoJackFunctionAndID;
case MDC1200:
MDC1200ID originalMDC1200ID = (MDC1200ID)id;
MDC1200ID copyMDC1200ID = new MDC1200ID();
copyMDC1200ID.setIdent( originalMDC1200ID.getIdent() );
return copyMDC1200ID;
case MIN:
Min originalMin = (Min)id;
Min copyMin = new Min();
copyMin.setMin( originalMin.getMin() );
return copyMin;
case MPT1327:
MPT1327ID originalMPT1327ID = (MPT1327ID)id;
MPT1327ID copyMPT1327ID = new MPT1327ID();
copyMPT1327ID.setIdent( originalMPT1327ID.getIdent() );
return copyMPT1327ID;
case NON_RECORDABLE:
return new NonRecordable();
case PRIORITY:
Priority originalPriority = (Priority)id;
Priority copyPriority = new Priority();
copyPriority.setPriority( originalPriority.getPriority() );
return copyPriority;
case SITE:
SiteID originalSiteID = (SiteID)id;
SiteID copySiteID = new SiteID();
copySiteID.setSite( originalSiteID.getSite() );
return copySiteID;
case TALKGROUP:
TalkgroupID originalTalkgroupID = (TalkgroupID)id;
TalkgroupID copyTalkgroupID = new TalkgroupID();
copyTalkgroupID.setTalkgroup( originalTalkgroupID.getTalkgroup() );
return copyTalkgroupID;
case STATUS:
StatusID originalStatusID = (StatusID)id;
StatusID copyStatusID = new StatusID();
copyStatusID.setStatus( originalStatusID.getStatus() );
return copyStatusID;
default:
}
public static AliasID copyOf(AliasID id)
{
switch(id.getType())
{
case BROADCAST_CHANNEL:
BroadcastChannel originalBroadcast = (BroadcastChannel)id;
BroadcastChannel copyBroadcast = new BroadcastChannel();
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
return copyBroadcast;
case ESN:
Esn originalESN = (Esn)id;
Esn copyESN = new Esn();
copyESN.setEsn(originalESN.getEsn());
return copyESN;
case LTR_NET_UID:
UniqueID originalUniqueID = (UniqueID)id;
UniqueID copyUniqueID = new UniqueID();
copyUniqueID.setUid(originalUniqueID.getUid());
return copyUniqueID;
case LOJACK:
LoJackFunctionAndID originalLoJackFunctionAndID = (LoJackFunctionAndID)id;
LoJackFunctionAndID copyLoJackFunctionAndID = new LoJackFunctionAndID();
copyLoJackFunctionAndID.setFunction(originalLoJackFunctionAndID.getFunction());
copyLoJackFunctionAndID.setID(originalLoJackFunctionAndID.getID());
return copyLoJackFunctionAndID;
case MIN:
Min originalMin = (Min)id;
Min copyMin = new Min();
copyMin.setMin(originalMin.getMin());
return copyMin;
case PRIORITY:
Priority originalPriority = (Priority)id;
Priority copyPriority = new Priority();
copyPriority.setPriority(originalPriority.getPriority());
return copyPriority;
case RADIO_ID:
Radio originalRadio = (Radio)id;
Radio copyRadio = new Radio(originalRadio.getProtocol(), originalRadio.getValue());
return copyRadio;
case RADIO_ID_RANGE:
RadioRange originalRadioRange = (RadioRange)id;
RadioRange copyRadioRange = new RadioRange(originalRadioRange.getProtocol(), originalRadioRange.getMinRadio(),
originalRadioRange.getMaxRadio());
return copyRadioRange;
case RECORD:
return new Record();
case SITE:
SiteID originalSiteID = (SiteID)id;
SiteID copySiteID = new SiteID();
copySiteID.setSite(originalSiteID.getSite());
return copySiteID;
case STATUS:
StatusID originalStatusID = (StatusID)id;
StatusID copyStatusID = new StatusID();
copyStatusID.setStatus(originalStatusID.getStatus());
return copyStatusID;
case TALKGROUP:
Talkgroup originalTalkgroup = (Talkgroup)id;
Talkgroup copyTalkgroup = new Talkgroup(originalTalkgroup.getProtocol(), originalTalkgroup.getValue());
return copyTalkgroup;
case TALKGROUP_RANGE:
TalkgroupRange originalRange = (TalkgroupRange)id;
TalkgroupRange copyRange = new TalkgroupRange(originalRange.getProtocol(), originalRange.getMinTalkgroup(),
originalRange.getMaxTalkgroup());
return copyRange;
return null;
}
public static AliasAction copyOf( AliasAction action )
{
if( action instanceof BeepAction )
{
return new BeepAction();
}
else if( action instanceof ClipAction)
{
ClipAction originalClip = (ClipAction)action;
ClipAction copyClip = new ClipAction();
copyClip.setInterval( originalClip.getInterval() );
copyClip.setPath( originalClip.getPath() );
copyClip.setPeriod( originalClip.getPeriod() );
return copyClip;
}
else if( action instanceof ScriptAction )
{
ScriptAction originalScript = (ScriptAction)action;
ScriptAction copyScript = new ScriptAction();
copyScript.setInterval( originalScript.getInterval() );
copyScript.setPeriod( originalScript.getPeriod() );
copyScript.setScript( originalScript.getScript() );
return copyScript;
}
return null;
}
public static Alias copyOf( Alias original )
{
Alias copy = new Alias( original.getName() );
copy.setList( original.getList() );
copy.setGroup( original.getGroup() );
copy.setColor( original.getColor() );
copy.setIconName( original.getIconName() );
for( AliasID id: original.getId() )
{
AliasID copyID = copyOf( id );
if( copyID != null )
{
copy.addAliasID( copyID );
}
}
//Legacy identifiers ... not supported
case FLEETSYNC:
case LEGACY_TALKGROUP:
case MDC1200:
case MPT1327:
case NON_RECORDABLE:
default:
}
for( AliasAction action: original.getAction() )
{
AliasAction copyAction = copyOf( action );
if( copyAction != null )
{
copy.addAliasAction( copyAction );
}
}
return copy;
}
return null;
}
public static AliasAction copyOf(AliasAction action)
{
if(action instanceof BeepAction)
{
return new BeepAction();
}
else if(action instanceof ClipAction)
{
ClipAction originalClip = (ClipAction)action;
ClipAction copyClip = new ClipAction();
copyClip.setInterval(originalClip.getInterval());
copyClip.setPath(originalClip.getPath());
copyClip.setPeriod(originalClip.getPeriod());
return copyClip;
}
else if(action instanceof ScriptAction)
{
ScriptAction originalScript = (ScriptAction)action;
ScriptAction copyScript = new ScriptAction();
copyScript.setInterval(originalScript.getInterval());
copyScript.setPeriod(originalScript.getPeriod());
copyScript.setScript(originalScript.getScript());
return copyScript;
}
return null;
}
public static Alias copyOf(Alias original)
{
Alias copy = new Alias(original.getName());
copy.setList(original.getList());
copy.setGroup(original.getGroup());
copy.setColor(original.getColor());
copy.setIconName(original.getIconName());
for(AliasID id : original.getId())
{
AliasID copyID = copyOf(id);
if(copyID != null)
{
copy.addAliasID(copyID);
}
}
for(AliasAction action : original.getAction())
{
AliasAction copyAction = copyOf(action);
if(copyAction != null)
{
copy.addAliasAction(copyAction);
}
}
return copy;
}
public static Editor<AliasID> getEditor(AliasID aliasID, BroadcastModel broadcastModel)
{
if(aliasID != null)
{
switch(aliasID.getType())
{
case BROADCAST_CHANNEL:
return new BroadcastChannelEditor(aliasID, broadcastModel);
case ESN:
return new ESNEditor(aliasID);
case LTR_NET_UID:
return new UniqueIDEditor(aliasID);
case LOJACK:
return new LoJackIDEditor(aliasID);
case MIN:
return new MINEditor(aliasID);
case PRIORITY:
return new PriorityEditor(aliasID);
case RADIO_ID:
return new RadioEditor(aliasID);
case RADIO_ID_RANGE:
return new RadioRangeEditor(aliasID);
case RECORD:
return new RecordEditor(aliasID);
case SITE:
return new SiteIDEditor(aliasID);
case STATUS:
return new StatusIDEditor(aliasID);
case TALKGROUP:
return new TalkgroupEditor(aliasID);
case TALKGROUP_RANGE:
return new TalkgroupRangeEditor(aliasID);
case FLEETSYNC:
return new FleetsyncIDEditor(aliasID);
case LEGACY_TALKGROUP:
return new LegacyTalkgroupIDEditor(aliasID);
case MDC1200:
return new MDC1200IDEditor(aliasID);
case MPT1327:
return new MPT1327IDEditor(aliasID);
case NON_RECORDABLE:
return new NonRecordableEditor(aliasID);
default:
break;
}
}
return new EmptyEditor<AliasID>();
}
public static Editor<AliasAction> getEditor(AliasAction aliasAction)
{
if(aliasAction != null)
{
switch(aliasAction.getType())
{
case BEEP:
return new BeepActionEditor(aliasAction);
case CLIP:
return new ClipActionEditor(aliasAction);
case SCRIPT:
return new ScriptActionEditor(aliasAction);
default:
break;
}
}
return new EmptyEditor<AliasAction>();
}
public static AliasID getAliasID(AliasIDType type)
{
switch(type)
{
case BROADCAST_CHANNEL:
return new BroadcastChannel();
case ESN:
return new Esn();
case FLEETSYNC:
return new FleetsyncID();
case LTR_NET_UID:
return new UniqueID();
case LOJACK:
return new LoJackFunctionAndID();
case MDC1200:
return new MDC1200ID();
case MIN:
return new Min();
case MPT1327:
return new MPT1327ID();
case NON_RECORDABLE:
return new NonRecordable();
case PRIORITY:
return new Priority();
case RADIO_ID:
return new Radio();
case RADIO_ID_RANGE:
return new RadioRange();
case RECORD:
return new Record();
case TALKGROUP:
return new Talkgroup();
case TALKGROUP_RANGE:
return new TalkgroupRange();
case SITE:
return new SiteID();
case STATUS:
return new StatusID();
case LEGACY_TALKGROUP:
return new LegacyTalkgroupID();
default:
throw new IllegalArgumentException("Unrecognized Alias ID type: " + type);
}
}
public static AliasAction getAliasAction(AliasActionType type)
{
switch(type)
{
case BEEP:
return new BeepAction();
case CLIP:
return new ClipAction();
case SCRIPT:
return new ScriptAction();
default:
throw new IllegalArgumentException("Unrecognized Alias Action type: " + type);
}
}
public static Editor<AliasID> getEditor( AliasID aliasID, BroadcastModel broadcastModel )
{
if( aliasID != null )
{
switch( aliasID.getType() )
{
case BROADCAST_CHANNEL:
return new BroadcastChannelEditor(aliasID, broadcastModel);
case ESN:
return new ESNEditor( aliasID );
case FLEETSYNC:
return new FleetsyncIDEditor( aliasID );
case LTR_NET_UID:
return new UniqueIDEditor( aliasID );
case LOJACK:
return new LoJackIDEditor( aliasID );
case MDC1200:
return new MDC1200IDEditor( aliasID );
case MIN:
return new MINEditor( aliasID );
case MPT1327:
return new MPT1327IDEditor( aliasID );
case NON_RECORDABLE:
return new NonRecordableEditor( aliasID );
case PRIORITY:
return new PriorityEditor( aliasID );
case SITE:
return new SiteIDEditor( aliasID );
case STATUS:
return new StatusIDEditor( aliasID );
case TALKGROUP:
return new TalkgroupIDEditor( aliasID );
default:
break;
}
}
return new EmptyEditor<AliasID>();
}
public static Editor<AliasAction> getEditor( AliasAction aliasAction )
{
if( aliasAction != null )
{
switch( aliasAction.getType() )
{
case BEEP:
return new BeepActionEditor( aliasAction );
case CLIP:
return new ClipActionEditor( aliasAction );
case SCRIPT:
return new ScriptActionEditor( aliasAction );
default:
break;
}
}
return new EmptyEditor<AliasAction>();
}
public static AliasID getAliasID( AliasIDType type )
{
switch( type )
{
case BROADCAST_CHANNEL:
return new BroadcastChannel();
case ESN:
return new Esn();
case FLEETSYNC:
return new FleetsyncID();
case LTR_NET_UID:
return new UniqueID();
case LOJACK:
return new LoJackFunctionAndID();
case MDC1200:
return new MDC1200ID();
case MIN:
return new Min();
case MPT1327:
return new MPT1327ID();
case NON_RECORDABLE:
return new NonRecordable();
case PRIORITY:
return new Priority();
case SITE:
return new SiteID();
case STATUS:
return new StatusID();
case TALKGROUP:
return new TalkgroupID();
default:
throw new IllegalArgumentException( "Unrecognized Alias ID type: " + type );
}
}
public static AliasAction getAliasAction( AliasActionType type )
{
switch( type )
{
case BEEP:
return new BeepAction();
case CLIP:
return new ClipAction();
case SCRIPT:
return new ScriptAction();
default:
throw new IllegalArgumentException( "Unrecognized Alias Action type: " + type );
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +1,27 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
@@ -30,7 +34,9 @@ import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Alias Model
* Alias Model contains all aliases and is responsible for creation and management of alias lists. Alias lists are a
* set of aliases that all share a common alias list name and can be attached to a decoding channel for aliasing
* identifiers produced by channel decoder(s).
*/
public class AliasModel extends AbstractTableModel
{
@@ -68,31 +74,69 @@ public class AliasModel extends AbstractTableModel
return null;
}
/**
* Returns an optional alias list associated with the identifier collection
*
* @return alias list or null
*/
public AliasList getAliasList(IdentifierCollection identifierCollection)
{
if(identifierCollection != null)
{
return getAliasList(identifierCollection.getAliasListConfiguration());
}
return null;
}
/**
* Retrieves an alias list specified by the alias list configuration identifier
*
* @param configurationIdentifier containing the name of an alias list
* @return alias list or null.
*/
public AliasList getAliasList(AliasListConfigurationIdentifier configurationIdentifier)
{
if(configurationIdentifier != null && configurationIdentifier.isValid())
{
return getAliasList(configurationIdentifier.getValue());
}
return null;
}
/**
* Creates a new alias list containing all aliases that match the alias name, or returns a previously created and
* cached alias list. Returned alias list is automatically registered as a listener to this model so that any
* updates to the list by the user will automatically be reflected in constructed alias lists.
*/
public AliasList getAliasList(String name)
{
if(name != null && mAliasListMap.containsKey(name))
if(name == null || name.isEmpty())
{
return new AliasList(name);
}
if(mAliasListMap.containsKey(name))
{
return mAliasListMap.get(name);
}
AliasList aliasList = new AliasList(name);
if(name != null)
for(Alias alias : mAliases)
{
for(Alias alias : mAliases)
if(alias.hasList() && alias.getList().equalsIgnoreCase(name))
{
if(alias.hasList() && alias.getList().equalsIgnoreCase(name))
{
aliasList.addAlias(alias);
}
aliasList.addAlias(alias);
}
mAliasListMap.put(name, aliasList);
//Register the new alias list to receive updates from this model
addListener(aliasList);
}
mAliasListMap.put(name, aliasList);
//Register the new alias list to receive updates from this model
addListener(aliasList);
return aliasList;
}

View File

@@ -1,379 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.alias;
import io.github.dsheirer.alias.action.AliasAction;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class PatchGroupAlias extends Alias
{
private final static Logger mLog = LoggerFactory.getLogger(PatchGroupAlias.class);
private Alias mPatchGroupAlias;
private List<Alias> mPatchedAliases = new ArrayList<>();
private List<String> mPatchedTalkgroupIDs = new ArrayList<>();
/**
* Patch group alias is a single alias for a group of aliases that are temporarily patched together. This will
* normally be used for patched talkgroups, but could be used with any type of alias identifiers that can be
* temporarily joined to form a patch group.
*
* Alias properties such as recordable, streamable and call priority are an aggregation from the set of patched
* aliases contained in this patch group alias and the patch group ID alias and where applicable, return the highest
* value/priority from the group of patched aliases.
*/
public PatchGroupAlias()
{
}
@Override
public int getColor()
{
if(mPatchGroupAlias != null)
{
return mPatchGroupAlias.getColor();
}
return super.getColor();
}
/**
* Sets an alias for this patch group ID
*/
public void setPatchGroupAlias(Alias alias)
{
mPatchGroupAlias = alias;
}
/**
* Alias for this patch group
*/
public Alias getPatchGroupAlias()
{
return mPatchGroupAlias;
}
/**
* Indicates if this patch group has an alias
*/
public boolean hasPatchGroupAlias()
{
return mPatchGroupAlias != null;
}
/**
* List of patched aliases contained in this patch group
*/
public List<Alias> getPatchedAliases()
{
return mPatchedAliases;
}
/**
* Removes/clears all patched aliases from this patch alias group
*/
public void clearPatchedAliases()
{
mPatchedAliases.clear();
}
/**
* Adds the patched alias to this patch alias group
*/
public void addPatchedAlias(Alias alias)
{
if(alias == PatchGroupAlias.this)
{
throw new IllegalArgumentException("Can't add patch group alias to itself - would create an infinite loop");
}
mPatchedAliases.add(alias);
}
/**
* Removes the patched alias from this patch alias group
*/
public void removePatchedAlias(Alias alias)
{
mPatchedAliases.remove(alias);
}
/**
* Sets the list of patched talkgroup IDs for this patch group
*/
public void setPatchedTalkgroupIDs(List<String> talkgroupIDs)
{
mPatchedTalkgroupIDs = talkgroupIDs;
}
/**
* List of patched talkgroup IDs for this patch group
*/
public List<String> getPatchedTalkgroupIDs()
{
return mPatchedTalkgroupIDs;
}
@Override
public String getName()
{
if(hasPatchGroupAlias())
{
return getPatchGroupAlias().getName();
}
else
{
for(AliasID id: getId())
{
if(id.getType() == AliasIDType.TALKGROUP)
{
return "PATCH:" + ((TalkgroupID)id).getTalkgroup();
}
}
}
return "PATCH:****";
}
@Override
public List<AliasAction> getAction()
{
List<AliasAction> aliasActions = new ArrayList<>();
if(hasPatchGroupAlias())
{
aliasActions.addAll(getPatchGroupAlias().getAction());
}
for(Alias alias: mPatchedAliases)
{
if(!(alias instanceof PatchGroupAlias))
{
for(AliasAction action: alias.getAction())
{
if(!aliasActions.contains(action))
{
aliasActions.add(action);
}
}
}
}
return aliasActions;
}
@Override
public boolean hasActions()
{
for(Alias alias: mPatchedAliases)
{
if(!(alias instanceof PatchGroupAlias) && alias.hasActions())
{
return true;
}
}
return hasPatchGroupAlias() && getPatchGroupAlias().hasActions();
}
/**
* Returns the highest listenable audio priority defined among the aliases in the group, or returns 'Do Not Monitor'
* if that is the only priority defined. Otherwise, returns default (100) priority.
*/
@Override
public int getCallPriority()
{
boolean hasDoNotMonitor = false;
int highestPriority = (hasPatchGroupAlias() ? getPatchGroupAlias().getCallPriority() : Priority.DEFAULT_PRIORITY + 1);
for(Alias alias: mPatchedAliases)
{
if(!(alias instanceof PatchGroupAlias) && alias.hasCallPriority() && alias.getCallPriority() < highestPriority)
{
if(alias.getCallPriority() == Priority.DO_NOT_MONITOR)
{
hasDoNotMonitor = true;
}
else
{
highestPriority = alias.getCallPriority();
}
}
}
if(highestPriority <= Priority.DEFAULT_PRIORITY)
{
return highestPriority;
}
else if(hasDoNotMonitor)
{
return Priority.DO_NOT_MONITOR;
}
else
{
return Priority.DEFAULT_PRIORITY;
}
}
@Override
public boolean hasCallPriority()
{
if(hasPatchGroupAlias() && getPatchGroupAlias().hasCallPriority())
{
return true;
}
for(Alias alias: mPatchedAliases)
{
if(!(alias instanceof PatchGroupAlias) && alias.hasCallPriority())
{
return true;
}
}
return false;
}
@Override
public boolean isRecordable()
{
if(hasPatchGroupAlias() && getPatchGroupAlias().isRecordable())
{
return true;
}
for(Alias alias: mPatchedAliases)
{
if(!(alias instanceof PatchGroupAlias) && alias.isRecordable())
{
return true;
}
}
return false;
}
@Override
public boolean isStreamable()
{
if(hasPatchGroupAlias() && getPatchGroupAlias().isStreamable())
{
return true;
}
for(Alias alias: mPatchedAliases)
{
if(!(alias instanceof PatchGroupAlias) && alias.isStreamable())
{
return true;
}
}
return false;
}
@Override
public Set<BroadcastChannel> getBroadcastChannels()
{
Set<BroadcastChannel> broadcastChannels = new TreeSet<>();
if(hasPatchGroupAlias())
{
broadcastChannels.addAll(getPatchGroupAlias().getBroadcastChannels());
}
for(Alias alias: mPatchedAliases)
{
if(!(alias instanceof PatchGroupAlias))
{
broadcastChannels.addAll(alias.getBroadcastChannels());
}
}
return broadcastChannels;
}
@Override
public boolean hasBroadcastChannel(String channel)
{
if(hasPatchGroupAlias() && getPatchGroupAlias().hasBroadcastChannel(channel))
{
return true;
}
for(Alias alias: mPatchedAliases)
{
if(!(alias instanceof PatchGroupAlias) && alias.hasBroadcastChannel(channel))
{
return true;
}
}
return false;
}
public static void main(String[] args)
{
Alias a1 = new Alias("Alias 1");
a1.addAliasID(new BroadcastChannel("Channel 1"));
a1.addAliasID(new BroadcastChannel("Channel 2"));
a1.addAliasID(new BroadcastChannel("Channel 3"));
a1.addAliasID(new BroadcastChannel("Channel 4"));
Alias a2 = new Alias("Alias 1");
a1.addAliasID(new BroadcastChannel("Channel 1"));
a1.addAliasID(new BroadcastChannel("Channel 2"));
a1.addAliasID(new BroadcastChannel("Channel 3"));
a1.addAliasID(new BroadcastChannel("Channel 4"));
Alias a3 = new Alias("Alias 1");
a1.addAliasID(new BroadcastChannel("Channel 1"));
a1.addAliasID(new BroadcastChannel("Channel 2"));
a1.addAliasID(new BroadcastChannel("Channel 3"));
a1.addAliasID(new BroadcastChannel("Channel 4"));
Alias a4 = new Alias("Alias 1");
a1.addAliasID(new BroadcastChannel("Channel 1"));
a1.addAliasID(new BroadcastChannel("Channel 2"));
a1.addAliasID(new BroadcastChannel("Channel 3"));
a1.addAliasID(new BroadcastChannel("Channel 4"));
PatchGroupAlias pga = new PatchGroupAlias();
pga.setPatchGroupAlias(a1);
pga.addPatchedAlias(a2);
pga.addPatchedAlias(a3);
pga.addPatchedAlias(a4);
Set<BroadcastChannel> bc = pga.getBroadcastChannels();
mLog.debug("Channels:" + bc);
}
}

View File

@@ -26,9 +26,7 @@ import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.action.beep.BeepAction;
import io.github.dsheirer.alias.action.clip.ClipAction;
import io.github.dsheirer.alias.action.script.ScriptAction;
import io.github.dsheirer.message.Message;
import java.util.concurrent.ScheduledExecutorService;
import io.github.dsheirer.message.IMessage;
/**
* Alias action defines an action to execute when an alias is detected active.
@@ -56,7 +54,7 @@ public abstract class AliasAction
* action attached. The alias argument is the parent alias containing the
* alias action.
*/
public abstract void execute(ScheduledExecutorService scheduledExecutorService, Alias alias, Message message);
public abstract void execute(Alias alias, IMessage message);
/**
* Dismiss a persistent alias action

View File

@@ -1,6 +1,7 @@
/*******************************************************************************
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,56 +15,94 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* *****************************************************************************
*/
package io.github.dsheirer.alias.action;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.IMessageListener;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* Manages all alias action events. Each received message is interrogated for
* any alias entries and then each alias is interrogated for any alias actions.
* Manages all alias action events. Each received message is interrogated for any identifiers that may be aliasable
* and have associated alias actions.
*
* Each alias action is executed and provided a copy of the source message so
* that the contents of the message can be used as part of the action.
* Each alias action is executed and provided a copy of the source message so that the contents of the message can be
* used as part of the action.
*/
public class AliasActionManager implements Listener<Message>
public class AliasActionManager extends Module implements IMessageListener, Listener<IMessage>
{
public AliasActionManager()
private final static Logger mLog = LoggerFactory.getLogger(AliasActionManager.class);
private AliasList mAliasList;
public AliasActionManager(AliasList aliasList)
{
mAliasList = aliasList;
}
@Override
public void receive(Message message)
public void receive(IMessage message)
{
if(message.isValid())
if(mAliasList != null && message.isValid())
{
List<Alias> aliases = message.getAliases();
List<Identifier> identifiers = message.getIdentifiers();
if(aliases != null)
for(Identifier identifier: identifiers)
{
for(Alias alias : aliases)
{
if(alias.hasActions())
{
List<AliasAction> actions = alias.getAction();
List<Alias> aliases = mAliasList.getAliases(identifier);
for(AliasAction action : actions)
for(Alias alias: aliases)
{
if(alias != null && alias.hasActions())
{
for(AliasAction action: alias.getAction())
{
/* Provide access to the thread pool manager in case the
* action needs to setup a timer, and provide the original
* message to be used as part of the action (e.g. sending
* the message as a text message to a cell phone */
action.execute(ThreadPool.SCHEDULED, alias, message);
action.execute(alias, message);
}
}
}
}
}
}
@Override
public void reset()
{
//No actions neeeded
}
@Override
public void start()
{
//No actions neeeded
}
@Override
public void stop()
{
//No actions neeeded
}
@Override
public void dispose()
{
//No actions neeeded
}
/**
* IMessageListener interface ... delegates to this class implementation of Listener<Message>
*/
@Override
public Listener<IMessage> getMessageListener()
{
return this;
}
}

View File

@@ -1,13 +1,33 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.action;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.util.ThreadPool;
import javax.swing.*;
import java.awt.*;
import java.util.concurrent.ScheduledExecutorService;
import javax.swing.JOptionPane;
import java.awt.EventQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -18,19 +38,15 @@ public abstract class RecurringAction extends AliasAction
protected AtomicBoolean mRunning = new AtomicBoolean(false);
@JsonIgnore
private ScheduledFuture<?> mPerpetualAction;
@JsonIgnore
protected ScheduledExecutorService mScheduledExecutorService;
protected Interval mInterval = Interval.ONCE;
protected int mPeriod = 5;
public abstract void performAction(Alias alias, Message message);
public abstract void performAction(Alias alias, IMessage message);
@Override
public void execute(ScheduledExecutorService scheduledExecutorService, Alias alias, Message message)
public void execute(Alias alias, IMessage message)
{
mScheduledExecutorService = scheduledExecutorService;
if(mRunning.compareAndSet(false, true))
{
switch(mInterval)
@@ -41,10 +57,10 @@ public abstract class RecurringAction extends AliasAction
break;
case DELAYED_RESET:
performThreadedAction(alias, message);
mScheduledExecutorService.schedule(new ResetTask(), mPeriod, TimeUnit.SECONDS);
ThreadPool.SCHEDULED.schedule(new ResetTask(), mPeriod, TimeUnit.SECONDS);
break;
case UNTIL_DISMISSED:
mPerpetualAction = mScheduledExecutorService.scheduleAtFixedRate(
mPerpetualAction = ThreadPool.SCHEDULED.scheduleAtFixedRate(
new PerformActionTask(alias, message), mPeriod, mPeriod, TimeUnit.SECONDS);
StringBuilder sb = new StringBuilder();
@@ -56,18 +72,13 @@ public abstract class RecurringAction extends AliasAction
final String text = sb.toString();
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
JOptionPane.showMessageDialog(null, text,
"Alias Alert", JOptionPane.INFORMATION_MESSAGE);
EventQueue.invokeLater(() -> {
JOptionPane.showMessageDialog(null, text,
"Alias Alert", JOptionPane.INFORMATION_MESSAGE);
dismiss(false);
dismiss(false);
mScheduledExecutorService.schedule(new ResetTask(), 15, TimeUnit.SECONDS);
}
ThreadPool.SCHEDULED.schedule(new ResetTask(), 15, TimeUnit.SECONDS);
});
break;
default:
@@ -79,16 +90,9 @@ public abstract class RecurringAction extends AliasAction
* Spawns the performAction() event into a new thread so that it doesn't
* delay any decoder actions.
*/
private void performThreadedAction(final Alias alias, final Message message)
private void performThreadedAction(final Alias alias, final IMessage message)
{
mScheduledExecutorService.schedule(new Runnable()
{
@Override
public void run()
{
performAction(alias, message);
}
}, 0, TimeUnit.SECONDS);
ThreadPool.SCHEDULED.schedule(() -> performAction(alias, message), 0, TimeUnit.SECONDS);
}
@Override
@@ -139,7 +143,7 @@ public abstract class RecurringAction extends AliasAction
private String mLabel;
private Interval(String label)
Interval(String label)
{
mLabel = label;
}
@@ -153,9 +157,9 @@ public abstract class RecurringAction extends AliasAction
public class PerformActionTask implements Runnable
{
private Alias mAlias;
private Message mMessage;
private IMessage mMessage;
public PerformActionTask(Alias alias, Message message)
public PerformActionTask(Alias alias, IMessage message)
{
mAlias = alias;
mMessage = message;

View File

@@ -1,10 +1,30 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.action.beep;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.action.AliasActionType;
import io.github.dsheirer.alias.action.RecurringAction;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.message.IMessage;
public class BeepAction extends RecurringAction
{
@@ -17,7 +37,7 @@ public class BeepAction extends RecurringAction
}
@Override
public void performAction(Alias alias, Message message )
public void performAction(Alias alias, IMessage message )
{
System.out.println( "\007" );
}

View File

@@ -1,3 +1,23 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.action.clip;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -5,7 +25,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.action.AliasActionType;
import io.github.dsheirer.alias.action.RecurringAction;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.message.IMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,7 +65,7 @@ public class ClipAction extends RecurringAction
}
@Override
public void performAction(Alias alias, Message message)
public void performAction(Alias alias, IMessage message)
{
try
{

View File

@@ -1,10 +1,30 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.action.script;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.action.AliasActionType;
import io.github.dsheirer.alias.action.RecurringAction;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.message.IMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,7 +59,7 @@ public class ScriptAction extends RecurringAction
}
@Override
public void performAction(Alias alias, Message message)
public void performAction(Alias alias, IMessage message)
{
try
{
@@ -63,8 +83,7 @@ public class ScriptAction extends RecurringAction
int exitCode = p.waitFor();
BufferedReader reader = new BufferedReader(
new InputStreamReader(p.getInputStream()));
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = "";
@@ -77,8 +96,7 @@ public class ScriptAction extends RecurringAction
if(exitCode != 0)
{
throw new RuntimeException("Exit Code: " + exitCode +
" Console:" + sb.toString());
throw new RuntimeException("Exit Code: " + exitCode + " Console:" + sb.toString());
}
}
}

View File

@@ -1,20 +1,24 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.alias.id;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -23,46 +27,56 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.mdc.MDC1200ID;
import io.github.dsheirer.alias.id.mobileID.Min;
import io.github.dsheirer.alias.id.mpt1327.MPT1327ID;
import io.github.dsheirer.alias.id.nonrecordable.NonRecordable;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.siteID.SiteID;
import io.github.dsheirer.alias.id.radio.Radio;
import io.github.dsheirer.alias.id.radio.RadioRange;
import io.github.dsheirer.alias.id.record.Record;
import io.github.dsheirer.alias.id.status.StatusID;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupID;
import io.github.dsheirer.alias.id.uniqueID.UniqueID;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include= JsonTypeInfo.As.PROPERTY, property="type")
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value=BroadcastChannel.class, name="broadcastChannel"),
@JsonSubTypes.Type(value=Esn.class, name="esn"),
@JsonSubTypes.Type(value=FleetsyncID.class, name="fleetsyncID"),
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
@JsonSubTypes.Type(value = LoJackFunctionAndID.class, name = "loJackFunctionAndID"),
@JsonSubTypes.Type(value = MDC1200ID.class, name = "mdc1200ID"),
@JsonSubTypes.Type(value = Min.class, name = "min"),
@JsonSubTypes.Type(value = MPT1327ID.class, name = "mpt1327ID"),
@JsonSubTypes.Type(value = NonRecordable.class, name = "nonRecordable"),
@JsonSubTypes.Type(value = Radio.class, name = "radio"),
@JsonSubTypes.Type(value = RadioRange.class, name = "radioRange"),
@JsonSubTypes.Type(value = Talkgroup.class, name = "talkgroup"),
@JsonSubTypes.Type(value = TalkgroupRange.class, name = "talkgroupRange"),
@JsonSubTypes.Type(value = Priority.class, name = "priority"),
@JsonSubTypes.Type(value = Record.class, name = "record"),
@JsonSubTypes.Type(value = SiteID.class, name = "siteID"),
@JsonSubTypes.Type(value = StatusID.class, name = "statusID"),
@JsonSubTypes.Type(value = TalkgroupID.class, name = "talkgroupID"),
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
@JsonSubTypes.Type(value = UniqueID.class, name = "uniqueID")
})
@JacksonXmlRootElement( localName = "id" )
@JacksonXmlRootElement(localName = "id")
public abstract class AliasID
{
public AliasID()
{
}
public AliasID()
{
}
@JsonIgnore
public abstract AliasIDType getType();
public abstract boolean matches( AliasID id );
@JsonIgnore
public abstract AliasIDType getType();
@JsonIgnore
public abstract boolean isValid();
public abstract boolean matches(AliasID id);
@JsonIgnore
public abstract boolean isValid();
}

View File

@@ -1,49 +1,59 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.alias.id;
public enum AliasIDType
{
ESN( "ESN" ),
FLEETSYNC( "Fleetsync" ),
LOJACK( "LoJack" ),
LTR_NET_UID( "LTR-Net UID" ),
MDC1200( "MDC-1200" ),
MIN( "Passport MIN" ),
MPT1327( "MPT-1327" ),
SITE( "Site" ),
STATUS( "Status" ),
TALKGROUP( "Talkgroup" ),
BROADCAST_CHANNEL("Audio Broadcast Channel"),
ESN("ESN"),
INVERT("Audio Inversion"),
LOJACK("LoJack"),
LTR_NET_UID("LTR-Net UID"),
MIN("Passport MIN"),
PRIORITY("Audio Priority"),
RADIO_ID("Radio ID"),
RADIO_ID_RANGE("Radio ID Range"),
RECORD("Record"),
SITE("Site"),
STATUS("Status"),
TALKGROUP("Talkgroup"),
TALKGROUP_RANGE("Talkgroup Range"),
INVERT( "Audio Inversion" ),
PRIORITY( "Audio Priority" ),
NON_RECORDABLE( "Audio Non-Recordable" ),
BROADCAST_CHANNEL( "Audio Broadcast Channel" );
private String mLabel;
private AliasIDType( String label )
{
mLabel = label;
}
public String toString()
{
return mLabel;
}
//Legacy identifier types - no longer used
FLEETSYNC("Fleetsync"),
LEGACY_TALKGROUP("Legacy Talkgroup"),
MDC1200("MDC-1200"),
MPT1327("MPT-1327"),
NON_RECORDABLE("Audio Non-Recordable");
private String mLabel;
AliasIDType(String label)
{
mLabel = label;
}
public String toString()
{
return mLabel;
}
}

View File

@@ -1,20 +1,24 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.alias.id;
import io.github.dsheirer.alias.Alias;
@@ -26,10 +30,19 @@ import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.gui.editor.EmptyEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
@@ -40,328 +53,325 @@ import java.util.List;
public class AliasIdentifierEditor extends Editor<Alias>
{
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;
private static ListModel<AliasID> EMPTY_MODEL = new DefaultListModel<>();
private JList<AliasID> mAliasIDList = new JList<>( EMPTY_MODEL );
private JButton mNewIDButton;
private JButton mCloneIDButton;
private JButton mDeleteIDButton;
private EditorContainer mEditorContainer = new EditorContainer();
private static ListModel<AliasID> EMPTY_MODEL = new DefaultListModel<>();
private JList<AliasID> mAliasIDList = new JList<>(EMPTY_MODEL);
private JButton mNewIDButton;
private JButton mCloneIDButton;
private JButton mDeleteIDButton;
private EditorContainer mEditorContainer = new EditorContainer();
private AliasModel mAliasModel;
private AliasModel mAliasModel;
private BroadcastModel mBroadcastModel;
public AliasIdentifierEditor( AliasModel aliasModel, BroadcastModel broadcastModel )
{
mAliasModel = aliasModel;
public AliasIdentifierEditor(AliasModel aliasModel, BroadcastModel broadcastModel)
{
mAliasModel = aliasModel;
mBroadcastModel = broadcastModel;
init();
}
init();
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 3",
"[grow,fill][grow,fill][grow,fill]", "[][grow,fill][]" ) );
private void init()
{
setLayout(new MigLayout("fill,wrap 3",
"[grow,fill][grow,fill][grow,fill]", "[][grow,fill][]"));
mAliasIDList.setVisibleRowCount( 6 );
mAliasIDList.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
mAliasIDList.setLayoutOrientation( JList.VERTICAL );
mAliasIDList.addListSelectionListener( new ListSelectionListener()
{
@Override
public void valueChanged( ListSelectionEvent event )
{
if( !event.getValueIsAdjusting() )
{
JList<?> list = (JList<?>)event.getSource();
Object selectedItem = list.getSelectedValue();
if( selectedItem != null && selectedItem instanceof AliasID )
{
AliasID selected = (AliasID)selectedItem;
mAliasIDList.setVisibleRowCount(6);
mAliasIDList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mAliasIDList.setLayoutOrientation(JList.VERTICAL);
mAliasIDList.addListSelectionListener(new ListSelectionListener()
{
@Override
public void valueChanged(ListSelectionEvent event)
{
if(!event.getValueIsAdjusting())
{
JList<?> list = (JList<?>)event.getSource();
mEditorContainer.setAliasID( selected );
mCloneIDButton.setEnabled( true );
mDeleteIDButton.setEnabled( true );
}
else
{
mCloneIDButton.setEnabled( false );
mDeleteIDButton.setEnabled( false );
}
}
}
} );
JScrollPane scroller = new JScrollPane( mAliasIDList );
add( scroller, "span,grow" );
add( mEditorContainer, "span,grow" );
Object selectedItem = list.getSelectedValue();
mNewIDButton = new JButton( "New ..." );
mNewIDButton.setToolTipText( "Create a new Alias Identifier" );
mNewIDButton.addMouseListener( new MouseAdapter()
{
@Override
public void mouseClicked( MouseEvent e )
{
JPopupMenu menu = new JPopupMenu();
if(selectedItem != null && selectedItem instanceof AliasID)
{
AliasID selected = (AliasID)selectedItem;
menu.add( new AddAliasIdentifierItem( AliasIDType.ESN ) );
menu.add( new AddAliasIdentifierItem( AliasIDType.FLEETSYNC) );
menu.add( new AddAliasIdentifierItem( AliasIDType.LOJACK) );
menu.add( new AddAliasIdentifierItem( AliasIDType.LTR_NET_UID) );
menu.add( new AddAliasIdentifierItem( AliasIDType.MDC1200 ) );
menu.add( new AddAliasIdentifierItem( AliasIDType.MPT1327 ) );
menu.add( new AddAliasIdentifierItem( AliasIDType.MIN ) );
menu.add( new AddAliasIdentifierItem( AliasIDType.SITE) );
menu.add( new AddAliasIdentifierItem( AliasIDType.STATUS) );
menu.add( new AddAliasIdentifierItem( AliasIDType.TALKGROUP) );
mEditorContainer.setAliasID(selected);
menu.addSeparator();
mCloneIDButton.setEnabled(true);
mDeleteIDButton.setEnabled(true);
}
else
{
mCloneIDButton.setEnabled(false);
mDeleteIDButton.setEnabled(false);
}
}
}
});
menu.add( new AddAliasIdentifierItem( AliasIDType.BROADCAST_CHANNEL) );
JScrollPane scroller = new JScrollPane(mAliasIDList);
add(scroller, "span,grow");
if( hasItem() )
{
Alias alias = getItem();
add(mEditorContainer, "span,grow");
if( alias.isRecordable() )
{
menu.add( new AddAliasIdentifierItem( AliasIDType.NON_RECORDABLE) );
}
mNewIDButton = new JButton("New ...");
mNewIDButton.setToolTipText("Create a new Alias Identifier");
mNewIDButton.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
JPopupMenu menu = new JPopupMenu();
if( !alias.hasCallPriority() )
{
menu.add( new AddAliasIdentifierItem( AliasIDType.PRIORITY) );
}
}
menu.add(new AddAliasIdentifierItem(AliasIDType.TALKGROUP));
menu.add(new AddAliasIdentifierItem(AliasIDType.TALKGROUP_RANGE));
menu.add(new AddAliasIdentifierItem(AliasIDType.RADIO_ID));
menu.add(new AddAliasIdentifierItem(AliasIDType.RADIO_ID_RANGE));
menu.add(new AddAliasIdentifierItem(AliasIDType.ESN));
// menu.add(new AddAliasIdentifierItem(AliasIDType.LOJACK));
menu.add(new AddAliasIdentifierItem(AliasIDType.STATUS));
menu.show( e.getComponent(), e.getX(), e.getY() );
}
} );
add( mNewIDButton );
mCloneIDButton = new JButton( "Clone" );
mCloneIDButton.setToolTipText( "Create a copy of the currently selected identifier" );
mCloneIDButton.setEnabled( false );
mCloneIDButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
AliasID selected = mAliasIDList.getSelectedValue();
if( selected != null )
{
AliasID clone = AliasFactory.copyOf( selected );
menu.addSeparator();
if( clone != null )
{
addAliasIDToList( clone );
}
}
}
} );
add( mCloneIDButton );
mDeleteIDButton = new JButton( "Delete" );
mDeleteIDButton.setToolTipText( "Delete the currently selected identifier" );
mDeleteIDButton.setEnabled( false );
mDeleteIDButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
final AliasID selected = mAliasIDList.getSelectedValue();
if( selected != null )
{
int choice = JOptionPane.showConfirmDialog( mDeleteIDButton,
"Do you want to delete [" + selected.toString() + "]",
"Are you sure?", JOptionPane.YES_NO_OPTION );
if( choice == JOptionPane.YES_OPTION && hasItem() )
{
Alias alias = getItem();
alias.removeAliasID( selected );
mAliasModel.broadcast( new AliasEvent( alias, AliasEvent.Event.CHANGE ) );
menu.add(new AddAliasIdentifierItem(AliasIDType.BROADCAST_CHANNEL));
setItem( alias );
}
}
}
} );
add( mDeleteIDButton, "wrap" );
}
@Override
public void setItem( Alias alias )
{
super.setItem( alias );
if( alias == null || alias.getId().isEmpty() )
{
mAliasIDList.setModel( EMPTY_MODEL );
}
else
{
DefaultListModel<AliasID> model = new DefaultListModel<AliasID>();
if(hasItem())
{
Alias alias = getItem();
List<AliasID> ids = alias.getId();
Collections.sort( ids, new Comparator<AliasID>()
{
@Override
public int compare( AliasID o1, AliasID o2 )
{
return o1.toString().compareTo( o2.toString() );
}
} );
for( AliasID id: ids )
{
model.addElement( id );
}
mAliasIDList.setModel( model );
}
mEditorContainer.setAliasID( null );
}
@Override
public void save()
{
if( isModified() || mEditorContainer.isModified() )
{
if( mEditorContainer.isModified() )
{
mEditorContainer.save();
}
setModified( false );
if( hasItem() )
{
mAliasModel.broadcast( new AliasEvent( getItem(), AliasEvent.Event.CHANGE ) );
}
}
}
public class EditorContainer extends JPanel
{
private static final long serialVersionUID = 1L;
if(!alias.isRecordable())
{
menu.add(new AddAliasIdentifierItem(AliasIDType.RECORD));
}
private Editor<AliasID> mEditor = new EmptyEditor<>();
if(!alias.hasCallPriority())
{
menu.add(new AddAliasIdentifierItem(AliasIDType.PRIORITY));
}
}
public EditorContainer()
{
setLayout( new MigLayout( "","[grow,fill]", "[grow,fill]" ) );
menu.show(e.getComponent(), e.getX(), e.getY());
}
});
add( mEditor );
}
public boolean isModified()
{
if( mEditor != null )
{
return mEditor.isModified();
}
return false;
}
public void save()
{
if( mEditor != null )
{
mEditor.save();
}
}
public void setAliasID( AliasID aliasID )
{
if( mEditor != null )
{
if( mEditor.isModified() )
{
int option = JOptionPane.showConfirmDialog(
EditorContainer.this,
"Identifier settings have changed. Do you want to save these changes?",
"Save Changes?",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE );
if( option == JOptionPane.YES_OPTION )
{
mEditor.save();
}
}
}
removeAll();
//This will always give us an editor
mEditor = AliasFactory.getEditor( aliasID, mBroadcastModel );
add( mEditor );
add(mNewIDButton);
revalidate();
}
}
private void addAliasIDToList( final AliasID id )
{
if( id != null && hasItem() )
{
final Alias alias = getItem();
alias.addAliasID( id );
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
setItem( alias );
mAliasIDList.setSelectedValue( id, true );
AliasIdentifierEditor.this.setModified( true );
}
} );
}
}
public class AddAliasIdentifierItem extends JMenuItem
{
private static final long serialVersionUID = 1L;
private AliasIDType mAliasIDType;
mCloneIDButton = new JButton("Clone");
mCloneIDButton.setToolTipText("Create a copy of the currently selected identifier");
mCloneIDButton.setEnabled(false);
mCloneIDButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
AliasID selected = mAliasIDList.getSelectedValue();
public AddAliasIdentifierItem( AliasIDType type )
{
super( type.toString() );
mAliasIDType = type;
addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
final AliasID id = AliasFactory.getAliasID( mAliasIDType );
addAliasIDToList( id );
}
} );
}
}
if(selected != null)
{
AliasID clone = AliasFactory.copyOf(selected);
if(clone != null)
{
addAliasIDToList(clone);
}
}
}
});
add(mCloneIDButton);
mDeleteIDButton = new JButton("Delete");
mDeleteIDButton.setToolTipText("Delete the currently selected identifier");
mDeleteIDButton.setEnabled(false);
mDeleteIDButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
final AliasID selected = mAliasIDList.getSelectedValue();
if(selected != null)
{
int choice = JOptionPane.showConfirmDialog(mDeleteIDButton,
"Do you want to delete [" + selected.toString() + "]",
"Are you sure?", JOptionPane.YES_NO_OPTION);
if(choice == JOptionPane.YES_OPTION && hasItem())
{
Alias alias = getItem();
alias.removeAliasID(selected);
mAliasModel.broadcast(new AliasEvent(alias, AliasEvent.Event.CHANGE));
setItem(alias);
}
}
}
});
add(mDeleteIDButton, "wrap");
}
@Override
public void setItem(Alias alias)
{
super.setItem(alias);
if(alias == null || alias.getId().isEmpty())
{
mAliasIDList.setModel(EMPTY_MODEL);
}
else
{
DefaultListModel<AliasID> model = new DefaultListModel<AliasID>();
List<AliasID> ids = alias.getId();
Collections.sort(ids, new Comparator<AliasID>()
{
@Override
public int compare(AliasID o1, AliasID o2)
{
return o1.toString().compareTo(o2.toString());
}
});
for(AliasID id : ids)
{
model.addElement(id);
}
mAliasIDList.setModel(model);
}
mEditorContainer.setAliasID(null);
}
@Override
public void save()
{
if(isModified() || mEditorContainer.isModified())
{
if(mEditorContainer.isModified())
{
mEditorContainer.save();
}
setModified(false);
if(hasItem())
{
mAliasModel.broadcast(new AliasEvent(getItem(), AliasEvent.Event.CHANGE));
}
}
}
public class EditorContainer extends JPanel
{
private static final long serialVersionUID = 1L;
private Editor<AliasID> mEditor = new EmptyEditor<>();
public EditorContainer()
{
setLayout(new MigLayout("", "[grow,fill]", "[grow,fill]"));
add(mEditor);
}
public boolean isModified()
{
if(mEditor != null)
{
return mEditor.isModified();
}
return false;
}
public void save()
{
if(mEditor != null)
{
mEditor.save();
}
}
public void setAliasID(AliasID aliasID)
{
if(mEditor != null)
{
if(mEditor.isModified())
{
int option = JOptionPane.showConfirmDialog(
EditorContainer.this,
"Identifier settings have changed. Do you want to save these changes?",
"Save Changes?",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);
if(option == JOptionPane.YES_OPTION)
{
mEditor.save();
}
}
}
removeAll();
//This will always give us an editor
mEditor = AliasFactory.getEditor(aliasID, mBroadcastModel);
add(mEditor);
revalidate();
}
}
private void addAliasIDToList(final AliasID id)
{
if(id != null && hasItem())
{
final Alias alias = getItem();
alias.addAliasID(id);
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
setItem(alias);
mAliasIDList.setSelectedValue(id, true);
AliasIdentifierEditor.this.setModified(true);
}
});
}
}
public class AddAliasIdentifierItem extends JMenuItem
{
private static final long serialVersionUID = 1L;
private AliasIDType mAliasIDType;
public AddAliasIdentifierItem(AliasIDType type)
{
super(type.toString());
mAliasIDType = type;
addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
final AliasID id = AliasFactory.getAliasID(mAliasIDType);
addAliasIDToList(id);
}
});
}
}
}

View File

@@ -48,7 +48,13 @@ public class Esn extends AliasID
public String toString()
{
return "ESN: " + mEsn;
StringBuilder sb = new StringBuilder();
sb.append("ESN: ").append(mEsn != null ? mEsn : "(empty)");
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
@Override

View File

@@ -1,21 +1,23 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.fleetsync;
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.legacy.fleetsync;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
@@ -48,7 +50,7 @@ public class FleetsyncID extends AliasID
public String toString()
{
return "Fleetsync: " + mID;
return "Fleetsync: " + (mID != null ? mID : "(empty)") + " **INVALID - USE TALKGROUP INSTEAD";
}
@Override

View File

@@ -1,21 +1,23 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.fleetsync;
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.legacy.fleetsync;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
@@ -23,9 +25,13 @@ import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.text.MaskFormatter;
import java.awt.*;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

View File

@@ -1,21 +1,23 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.mdc;
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.legacy.mdc;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
@@ -49,7 +51,7 @@ public class MDC1200ID extends AliasID
public String toString()
{
return "MDC-1200: " + mID;
return "MDC-1200: " + (mID != null ? mID : "(empty)") + " **INVALID - USE TALKGROUP INSTEAD**";
}
@Override

View File

@@ -0,0 +1,133 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.legacy.mdc;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.text.MaskFormatter;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class MDC1200IDEditor extends DocumentListenerEditor<AliasID>
{
private static final long serialVersionUID = 1L;
private static final String HELP_TEXT = "<html>"
+ "<h3>MDC-1200 Identifier</h3>"
+ "<b>MDC-1200:</b> four digit [0-9] value (e.g. <u>1234</u>)<br>"
+ "<b>Wildcard:</b> use an asterisk (*) to wildcard individual<br>"
+ "digits (e.g. <u>123*</u> or <u>**34</u>)"
+ "</html>";
private JTextField mTextField;
public MDC1200IDEditor(AliasID aliasID)
{
initGUI();
setItem(aliasID);
}
private void initGUI()
{
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
add(new JLabel("MDC-1200 ID:"));
MaskFormatter formatter = null;
try
{
//Mask: 4 digits
formatter = new MaskFormatter("####");
}
catch(Exception e)
{
//Do nothing, the mask was invalid
}
mTextField = new JFormattedTextField(formatter);
mTextField.getDocument().addDocumentListener(this);
mTextField.setToolTipText(HELP_TEXT);
add(mTextField, "growx,push");
JLabel help = new JLabel("Help ...");
help.setForeground(Color.BLUE.brighter());
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
help.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
JOptionPane.showMessageDialog(MDC1200IDEditor.this,
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE);
}
});
add(help, "align left");
}
public MDC1200ID getMDC1200ID()
{
if(getItem() instanceof MDC1200ID)
{
return (MDC1200ID)getItem();
}
return null;
}
@Override
public void setItem(AliasID aliasID)
{
super.setItem(aliasID);
MDC1200ID mdc = getMDC1200ID();
if(mdc != null)
{
mTextField.setText(mdc.getIdent());
}
setModified(false);
repaint();
}
@Override
public void save()
{
MDC1200ID mdc = getMDC1200ID();
if(mdc != null)
{
mdc.setIdent(mTextField.getText());
}
setModified(false);
}
}

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.mobileID;
package io.github.dsheirer.alias.id.legacy.mobileID;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.mobileID;
package io.github.dsheirer.alias.id.legacy.mobileID;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
@@ -52,7 +52,7 @@ public class Min extends AliasID
public String toString()
{
return "MIN: " + mMin;
return "MIN: " + (mMin != null ? mMin : "(empty)") + (isValid() ? "" : " **INVALID - USE TALKGROUP INSTEAD");
}
@Override

View File

@@ -1,21 +1,23 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.mpt1327;
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.legacy.mpt1327;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
@@ -49,7 +51,7 @@ public class MPT1327ID extends AliasID
public String toString()
{
return "MPT-1327: " + mID;
return "MPT-1327: " + (mID != null ? mID : "(empty)") + " **INVALID - USE TALKGROUP INSTEAD**";
}
@Override

View File

@@ -1,29 +1,35 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.mpt1327;
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.legacy.mpt1327;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.text.MaskFormatter;
import java.awt.*;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

View File

@@ -0,0 +1,53 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.legacy.nonrecordable;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
public class NonRecordable extends AliasID
{
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
@Override
public AliasIDType getType()
{
return AliasIDType.NON_RECORDABLE;
}
@Override
public boolean isValid()
{
return true;
}
@Override
public boolean matches(AliasID id)
{
return false;
}
@Override
public String toString()
{
return "Audio Non-Recordable - **INVALID - USE RECORD INSTEAD**";
}
}

View File

@@ -0,0 +1,78 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.legacy.nonrecordable;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class NonRecordableEditor extends DocumentListenerEditor<AliasID>
{
private static final long serialVersionUID = 1L;
private static final String HELP_TEXT = "<html>"
+ "<h3>Audio Non-Recordable</h3>"
+ "This identifies an alias as non-recordable.<br><br>"
+ "The alias can still be monitored but associated audio will<br>"
+ "not be recorded.<br><br>"
+ "The default behavior when audio recording is enabled for a<br>"
+ "channel is to record all aliases except any alias with a<br>"
+ "non-recordable identifier";
public NonRecordableEditor(AliasID aliasID)
{
initGUI();
setItem(aliasID);
}
private void initGUI()
{
setLayout(new MigLayout("fill,wrap 1", "[]", "[][]"));
add(new JLabel("Non-Recordable"), "wrap");
JLabel help = new JLabel("Help ...");
help.setForeground(Color.BLUE.brighter());
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
help.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
JOptionPane.showMessageDialog(NonRecordableEditor.this,
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE);
}
});
add(help, "align left");
}
@Override
public void save()
{
}
}

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.siteID;
package io.github.dsheirer.alias.id.legacy.siteID;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
@@ -48,7 +48,7 @@ public class SiteID extends AliasID
public String toString()
{
return "Site: " + mSite;
return "Site: " + (mSite != null ? mSite : "(empty)") + (isValid() ? "" : " **NOT VALID**");
}
@Override

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.siteID;
package io.github.dsheirer.alias.id.legacy.siteID;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;

View File

@@ -1,37 +1,39 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.talkgroup;
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.legacy.talkgroup;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
public class TalkgroupID extends AliasID
public class LegacyTalkgroupID extends AliasID
{
private String mTalkgroup;
public TalkgroupID()
public LegacyTalkgroupID()
{
//No arg JAXB constructor
}
public TalkgroupID(String talkgroup)
public LegacyTalkgroupID(String talkgroup)
{
mTalkgroup = talkgroup;
}
@@ -57,7 +59,7 @@ public class TalkgroupID extends AliasID
{
StringBuilder sb = new StringBuilder();
sb.append("Talkgroup: " + mTalkgroup);
sb.append("Legacy Talkgroup:").append(mTalkgroup).append(" *Disabled - Please use Talkgroup instead");
return sb.toString();
}
@@ -67,9 +69,9 @@ public class TalkgroupID extends AliasID
{
boolean retVal = false;
if(id instanceof TalkgroupID)
if(id instanceof LegacyTalkgroupID)
{
TalkgroupID tgid = (TalkgroupID)id;
LegacyTalkgroupID tgid = (LegacyTalkgroupID)id;
//Create a pattern - replace * wildcards with regex single-char wildcard
String pattern = mTalkgroup.replace("*", ".?");
@@ -84,6 +86,6 @@ public class TalkgroupID extends AliasID
@Override
public AliasIDType getType()
{
return AliasIDType.TALKGROUP;
return AliasIDType.LEGACY_TALKGROUP;
}
}

View File

@@ -0,0 +1,121 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.legacy.talkgroup;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class LegacyTalkgroupIDEditor extends DocumentListenerEditor<AliasID>
{
private static final long serialVersionUID = 1L;
private static final String HELP_TEXT =
"<html><h3>Talkgroup Identifier</h3>"
+ "<b>P25:</b> 4 or 6 hex characters (e.g. <u>AB12</u> or <u>ABC123</u>)<br>"
+ "<b>LTR:</b> A-HH-TTT where A=Area H=Home T=Talkgroup (<u>0-01-128</u>)<br>"
+ "<b>Passport</b>: 5-digit number (<u>12345</u> or <u>00023</u>)<br>"
+ "<br>"
+ "<b>Wildcard:</b> use an asterisk (*) in place of each talkgroup digit (e.g. <u>0*1*5</u>)"
+ "</html>";
private JTextField mTextField;
public LegacyTalkgroupIDEditor(AliasID aliasID)
{
initGUI();
setItem(aliasID);
}
private void initGUI()
{
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
add(new JLabel("Talkgroup:"));
mTextField = new JTextField();
mTextField.getDocument().addDocumentListener(this);
mTextField.setToolTipText(HELP_TEXT);
add(mTextField, "growx,push");
JLabel help = new JLabel("Help ...");
help.setForeground(Color.BLUE.brighter());
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
help.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
JOptionPane.showMessageDialog(LegacyTalkgroupIDEditor.this,
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE);
}
});
add(help, "align left");
}
public LegacyTalkgroupID getTalkgroupID()
{
if(getItem() instanceof LegacyTalkgroupID)
{
return (LegacyTalkgroupID)getItem();
}
return null;
}
@Override
public void setItem(AliasID aliasID)
{
super.setItem(aliasID);
LegacyTalkgroupID talkgroup = getTalkgroupID();
if(talkgroup != null)
{
mTextField.setText(talkgroup.getTalkgroup());
}
setModified(false);
repaint();
}
@Override
public void save()
{
LegacyTalkgroupID talkgroup = getTalkgroupID();
if(talkgroup != null)
{
talkgroup.setTalkgroup(mTextField.getText());
}
setModified(false);
}
}

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.uniqueID;
package io.github.dsheirer.alias.id.legacy.uniqueID;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
@@ -48,7 +48,7 @@ public class UniqueID extends AliasID
public String toString()
{
return "UniqueID: " + mUid;
return "UniqueID: " + mUid + " **INVALID - USE TALKGROUP INSTEAD";
}
@Override

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.uniqueID;
package io.github.dsheirer.alias.id.legacy.uniqueID;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;

View File

@@ -26,7 +26,7 @@ import io.github.dsheirer.module.decode.lj1200.LJ1200Message;
public class LoJackFunctionAndID extends AliasID
{
private LJ1200Message.Function mFunction = LJ1200Message.Function.F0_UNKNOWN;
private String mID = "*****";
private String mID = null;
public LoJackFunctionAndID()
{
@@ -62,7 +62,8 @@ public class LoJackFunctionAndID extends AliasID
public String toString()
{
return "LoJack FUNC: " + mFunction.getLabel() + " ID:" + (mID == null ? "" : mID);
return "LoJack FUNCTION: " + mFunction.getLabel() +
" ID:" + (mID == null ? "" : mID) + (isValid() ? "" : " **NOT VALID**");
}
/**

View File

@@ -1,127 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.mdc;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.text.MaskFormatter;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class MDC1200IDEditor extends DocumentListenerEditor<AliasID>
{
private static final long serialVersionUID = 1L;
private static final String HELP_TEXT = "<html>"
+ "<h3>MDC-1200 Identifier</h3>"
+ "<b>MDC-1200:</b> four digit [0-9] value (e.g. <u>1234</u>)<br>"
+ "<b>Wildcard:</b> use an asterisk (*) to wildcard individual<br>"
+ "digits (e.g. <u>123*</u> or <u>**34</u>)"
+ "</html>";
private JTextField mTextField;
public MDC1200IDEditor( AliasID aliasID )
{
initGUI();
setItem( aliasID );
}
private void initGUI()
{
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][]" ) );
add( new JLabel( "MDC-1200 ID:" ) );
MaskFormatter formatter = null;
try
{
//Mask: 4 digits
formatter = new MaskFormatter( "####" );
}
catch( Exception e )
{
//Do nothing, the mask was invalid
}
mTextField = new JFormattedTextField( formatter );
mTextField.getDocument().addDocumentListener( this );
mTextField.setToolTipText( HELP_TEXT );
add( mTextField, "growx,push" );
JLabel help = new JLabel( "Help ..." );
help.setForeground( Color.BLUE.brighter() );
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
help.addMouseListener( new MouseAdapter()
{
@Override
public void mouseClicked( MouseEvent e )
{
JOptionPane.showMessageDialog( MDC1200IDEditor.this,
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
}
} );
add( help, "align left" );
}
public MDC1200ID getMDC1200ID()
{
if( getItem() instanceof MDC1200ID )
{
return (MDC1200ID)getItem();
}
return null;
}
@Override
public void setItem( AliasID aliasID )
{
super.setItem( aliasID );
MDC1200ID mdc = getMDC1200ID();
if( mdc != null )
{
mTextField.setText( mdc.getIdent() );
}
setModified( false );
repaint();
}
@Override
public void save()
{
MDC1200ID mdc = getMDC1200ID();
if( mdc != null )
{
mdc.setIdent( mTextField.getText() );
}
setModified( false );
}
}

View File

@@ -1,33 +0,0 @@
package io.github.dsheirer.alias.id.nonrecordable;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
public class NonRecordable extends AliasID
{
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
@Override
public AliasIDType getType()
{
return AliasIDType.NON_RECORDABLE;
}
@Override
public boolean isValid()
{
return true;
}
@Override
public boolean matches(AliasID id)
{
return false;
}
@Override
public String toString()
{
return "Audio Non-Recordable";
}
}

View File

@@ -1,74 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.nonrecordable;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class NonRecordableEditor extends DocumentListenerEditor<AliasID>
{
private static final long serialVersionUID = 1L;
private static final String HELP_TEXT = "<html>"
+ "<h3>Audio Non-Recordable</h3>"
+ "This identifies an alias as non-recordable.<br><br>"
+ "The alias can still be monitored but associated audio will<br>"
+ "not be recorded.<br><br>"
+ "The default behavior when audio recording is enabled for a<br>"
+ "channel is to record all aliases except any alias with a<br>"
+ "non-recordable identifier";
public NonRecordableEditor( AliasID aliasID )
{
initGUI();
setItem( aliasID );
}
private void initGUI()
{
setLayout( new MigLayout( "fill,wrap 1", "[]", "[][]" ) );
add( new JLabel( "Non-Recordable" ), "wrap" );
JLabel help = new JLabel( "Help ..." );
help.setForeground( Color.BLUE.brighter() );
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
help.addMouseListener( new MouseAdapter()
{
@Override
public void mouseClicked( MouseEvent e )
{
JOptionPane.showMessageDialog( NonRecordableEditor.this,
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
}
} );
add( help, "align left" );
}
@Override
public void save()
{
}
}

View File

@@ -0,0 +1,118 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.alias.id.radio;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupFormat;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
/**
* Integer radio identifier with protocol.
*/
public class Radio extends AliasID
{
private Protocol mProtocol = Protocol.UNKNOWN;
private int mValue;
public Radio()
{
//No arg JAXB constructor
}
public Radio(Protocol protocol, int value)
{
mProtocol = protocol;
mValue = value;
}
@JacksonXmlProperty(isAttribute = true, localName = "value")
public int getValue()
{
return mValue;
}
public void setValue(int value)
{
mValue = value;
}
@JacksonXmlProperty(isAttribute = true, localName = "protocol")
public Protocol getProtocol()
{
return mProtocol;
}
public void setProtocol(Protocol protocol)
{
mProtocol = protocol;
}
@Override
public boolean isValid()
{
if(mProtocol == null || mProtocol == Protocol.UNKNOWN)
{
return false;
}
RadioFormat radioFormat = RadioFormat.get(mProtocol);
return radioFormat.getMinimumValidValue() <= mValue && mValue <= radioFormat.getMaximumValidValue();
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Radio ID:").append(TalkgroupFormatter.format(mProtocol, mValue));
sb.append(" Protocol:").append((mProtocol));
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
@Override
public boolean matches(AliasID id)
{
if(id instanceof Radio)
{
Radio tgid = (Radio)id;
return (getProtocol() == tgid.getProtocol()) && (getValue() == tgid.getValue());
}
return false;
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
@Override
public AliasIDType getType()
{
return AliasIDType.RADIO_ID;
}
}

View File

@@ -0,0 +1,235 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.alias.id.radio;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
import io.github.dsheirer.protocol.Protocol;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.text.MaskFormatter;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.ParseException;
public class RadioEditor extends DocumentListenerEditor<AliasID>
{
private final static Logger mLog = LoggerFactory.getLogger(RadioEditor.class);
private static final long serialVersionUID = 1L;
private static final String VALID_CHARACTERS_FOR_ASTERISK_MASKS = "0123456789 ";
private MaskFormatter mMaskFormatter = new MaskFormatter();
private JComboBox<Protocol> mComboProtocol;
private JFormattedTextField mRadioIdField;
public RadioEditor(AliasID aliasID)
{
initGUI();
setItem(aliasID);
}
private void initGUI()
{
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
add(new JLabel("Protocol:"));
mComboProtocol = new JComboBox<>();
DefaultComboBoxModel<Protocol> model = new DefaultComboBoxModel<>();
for(Protocol protocol : Protocol.RADIO_ID_PROTOCOLS)
{
model.addElement(protocol);
}
mComboProtocol.setModel(model);
mComboProtocol.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
Protocol protocol = (Protocol)mComboProtocol.getSelectedItem();
updateEditor(protocol);
setModified(true);
}
});
add(mComboProtocol);
add(new JLabel("Value:"));
mRadioIdField = new JFormattedTextField(mMaskFormatter);
mRadioIdField.getDocument().addDocumentListener(this);
add(mRadioIdField, "growx,push");
JLabel help = new JLabel("Help ...");
help.setForeground(Color.BLUE.brighter());
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
help.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
RadioFormat radioFormat = RadioFormat.get(getCurrentProtocol());
JOptionPane.showMessageDialog(RadioEditor.this,
radioFormat.getValidRangeHelpText(), "Help", JOptionPane.INFORMATION_MESSAGE);
}
});
add(help, "align left");
}
private void updateEditor(Protocol protocol)
{
RadioFormat mask = RadioFormat.get(protocol);
int currentValue = 0;
Radio radio = getRadio();
if(radio != null)
{
currentValue = radio.getValue();
}
try
{
mRadioIdField.setValue(null);
mRadioIdField.setToolTipText(mask.getValidRangeHelpText());
mMaskFormatter.setMask(mask.getMask());
if(mask.getMask().contains("*"))
{
mMaskFormatter.setValidCharacters(VALID_CHARACTERS_FOR_ASTERISK_MASKS);
}
mRadioIdField.setValue(RadioFormatter.format(protocol, currentValue));
}
catch(ParseException pe)
{
mLog.error("Error applying talkgroup editor mask to mask formatter [" + mask.getMask() + "]");
}
}
private Protocol getCurrentProtocol()
{
return mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
}
/**
* Current talkgroup alias id
*/
public Radio getRadio()
{
if(getItem() instanceof Radio)
{
return (Radio)getItem();
}
return null;
}
@Override
public void setItem(AliasID aliasID)
{
super.setItem(aliasID);
Radio radio = getRadio();
if(radio != null)
{
Protocol currentProtocol = getCurrentProtocol();
if(currentProtocol == radio.getProtocol())
{
updateEditor(radio.getProtocol());
}
mComboProtocol.getModel().setSelectedItem(radio.getProtocol());
String formatted = RadioFormatter.format(radio.getProtocol(), radio.getValue());
mRadioIdField.setValue(formatted);
}
else
{
mComboProtocol.getModel().setSelectedItem(Protocol.UNKNOWN);
mRadioIdField.setValue(RadioFormatter.format(getCurrentProtocol(), 0));
}
setModified(false);
repaint();
}
@Override
public void save()
{
Radio radio = getRadio();
if(radio != null)
{
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
if(protocol == null)
{
protocol = Protocol.UNKNOWN;
}
int value = -1;
try
{
value = RadioFormatter.parse(protocol, mRadioIdField.getText());
}
catch(ParseException pe)
{
//ignore ... value is still -1 and outside valid value range
}
RadioFormat mask = RadioFormat.get(protocol);
//Check for valid value within range ... notify user but allow value to persist
if(value < mask.getMinimumValidValue() || value > mask.getMaximumValidValue())
{
String message = "Invalid value [" + mRadioIdField.getText() + "]. " + protocol.name() +
" valid range is [" + mask.getValidRangeDescription() + "]";
JOptionPane.showMessageDialog(RadioEditor.this, message, "Invalid Radio ID Value",
JOptionPane.ERROR_MESSAGE);
}
else
{
radio.setValue(value);
}
radio.setProtocol(protocol);
}
setModified(false);
}
}

View File

@@ -0,0 +1,117 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.alias.id.radio;
import io.github.dsheirer.protocol.Protocol;
/**
* Radio ID protocol identifier entry masks and min/max valid ranges and valid range descriptions
*
* Note: the getMask() value is intended for use with a MaskFormatter attached to a JFormattedTextField. The
* protocol masks that have a dash (-) use the # (any digit) character and the protocol masks that do not have
* a formatting dash use an asterisk (*). Within the editor(s) that use this enumeration, the editor must use
* a setValidCharacters() on the mask formatter whenever it detects the mask containing one or more asterisks.
*/
public enum RadioFormat
{
APCO25("********", 0, 0xFFFFFF, "0 to 16,777,215",
"<html>APCO25 valid range is 0 to 16,777,215"),
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
"Unknown protocol valid value range is 1-16,777,215");
private String mMask;
private int mMinimumValue;
private int mMaximumValue;
private String mValidRangeDescription;
private String mValidRangeHelpText;
RadioFormat(String mask, int minimumValue, int maximumValue, String validRangeDescription, String validRangeHelpText)
{
mMask = mask;
mMinimumValue = minimumValue;
mMaximumValue = maximumValue;
mValidRangeDescription = validRangeDescription;
mValidRangeHelpText = validRangeHelpText;
}
public String getMask()
{
return mMask;
}
/**
* Minimum valid value for the protocol
*/
public int getMinimumValidValue()
{
return mMinimumValue;
}
/**
* Maximum valid value for the protocol
*/
public int getMaximumValidValue()
{
return mMaximumValue;
}
/**
* Indicates if the value is valid for the protocol
*/
public boolean isValid(int value)
{
return getMinimumValidValue() <= value && value <= getMaximumValidValue();
}
/**
* Short description of the valid value range
*/
public String getValidRangeDescription()
{
return mValidRangeDescription;
}
/**
* Help text for valid value ranges
*/
public String getValidRangeHelpText()
{
return mValidRangeHelpText;
}
public static RadioFormat get(Protocol protocol)
{
if(protocol == null)
{
return UNKNOWN;
}
switch(protocol)
{
case APCO25:
return APCO25;
default:
return UNKNOWN;
}
}
}

View File

@@ -0,0 +1,82 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.alias.id.radio;
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.IntegerFormatter;
import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
/**
* Protocol-specific formatting of radio id values and parsing of formatted radio id values.
*/
public class RadioFormatter
{
private final static Logger mLog = LoggerFactory.getLogger(RadioFormatter.class);
private static Map<Protocol,IntegerFormatter> mFormatterMap = new HashMap<>();
static
{
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new IntegerFormatter());
}
public RadioFormatter()
{
}
/**
* Parses the formatted value into an integer using a protocol specific formatter
*/
public static int parse(Protocol protocol, String value) throws ParseException
{
IntegerFormatter formatter = mFormatterMap.get(protocol);
if(formatter == null)
{
formatter = mFormatterMap.get(Protocol.UNKNOWN);
}
return formatter.parse(value);
}
/**
* Formats the integer value using a protocol-specific formatter
*/
public static String format(Protocol protocol, int value)
{
IntegerFormatter formatter = mFormatterMap.get(protocol);
if(formatter == null)
{
formatter = mFormatterMap.get(Protocol.UNKNOWN);
}
return formatter.format(value);
}
}

View File

@@ -0,0 +1,166 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.alias.id.radio;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.protocol.Protocol;
/**
* Integer radio identifier range of values with protocol.
*/
public class RadioRange extends AliasID
{
private Protocol mProtocol = Protocol.UNKNOWN;
private int mMinRadio;
private int mMaxRadio;
public RadioRange()
{
//No arg JAXB constructor
}
/**
* Creates a radio range of from - to radio values (inclusive) for the specified protocol
* @param protocol for the radio range
* @param minRadio starting or minimum radio value
* @param maxRadio ending or maximum radio value
*/
public RadioRange(Protocol protocol, int minRadio, int maxRadio)
{
mProtocol = protocol;
mMinRadio = minRadio;
mMaxRadio = maxRadio;
}
@JacksonXmlProperty(isAttribute = true, localName = "min")
public int getMinRadio()
{
return mMinRadio;
}
public void setMinRadio(int minRadio)
{
mMinRadio = minRadio;
}
@JacksonXmlProperty(isAttribute = true, localName = "max")
public int getMaxRadio()
{
return mMaxRadio;
}
public void setMaxRadio(int maxRadio)
{
mMaxRadio = maxRadio;
}
@JacksonXmlProperty(isAttribute = true, localName = "protocol")
public Protocol getProtocol()
{
return mProtocol;
}
public void setProtocol(Protocol protocol)
{
mProtocol = protocol;
}
@Override
public boolean isValid()
{
if(mProtocol == null || mProtocol == Protocol.UNKNOWN)
{
return false;
}
RadioFormat radioFormat = RadioFormat.get(mProtocol);
return radioFormat.getMinimumValidValue() <= mMinRadio &&
mMinRadio <= radioFormat.getMaximumValidValue() &&
radioFormat.getMinimumValidValue() <= mMaxRadio &&
mMaxRadio <= radioFormat.getMaximumValidValue();
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Radio ID Range:").append(RadioFormatter.format(mProtocol, mMinRadio));
sb.append(" to ").append(RadioFormatter.format(mProtocol, mMaxRadio));
sb.append(" Protocol:").append(mProtocol != null ? mProtocol : "unspecified");
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
@Override
public boolean matches(AliasID id)
{
if(id instanceof RadioRange)
{
RadioRange tgid = (RadioRange)id;
return (getProtocol() == tgid.getProtocol()) &&
(getMinRadio() == tgid.getMinRadio()) &&
(getMaxRadio() == tgid.getMaxRadio());
}
return false;
}
/**
* Indicates if this radio range contains/includes the value
* @param radioValue
* @return
*/
public boolean contains(int radioValue)
{
return getMinRadio() <= radioValue && radioValue <= getMaxRadio();
}
/**
* Indicates if this talkgroup range overlaps the talkgroup range argument.
* @param radioRange to check for overlap
* @return true if the ranges overlap
*/
public boolean overlaps(RadioRange radioRange)
{
return contains(radioRange.getMinRadio()) ||
contains(radioRange.getMaxRadio()) ||
(getMinRadio() < radioRange.getMinRadio() &&
radioRange.getMaxRadio() < getMaxRadio()) ||
(radioRange.getMinRadio() < getMinRadio() &&
getMaxRadio() < radioRange.getMaxRadio());
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
@Override
public AliasIDType getType()
{
return AliasIDType.RADIO_ID_RANGE;
}
}

View File

@@ -0,0 +1,273 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.alias.id.radio;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
import io.github.dsheirer.protocol.Protocol;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.text.MaskFormatter;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.ParseException;
public class RadioRangeEditor extends DocumentListenerEditor<AliasID>
{
private final static Logger mLog = LoggerFactory.getLogger(RadioRangeEditor.class);
private static final long serialVersionUID = 1L;
private static final String VALID_CHARACTERS_FOR_ASTERISK_MASKS = "0123456789 ";
private MaskFormatter mMaskFormatter = new MaskFormatter();
private JComboBox<Protocol> mComboProtocol;
private JFormattedTextField mMinRadioField;
private JFormattedTextField mMaxRadioField;
public RadioRangeEditor(AliasID aliasID)
{
initGUI();
setItem(aliasID);
}
private void initGUI()
{
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
add(new JLabel("Protocol:"));
mComboProtocol = new JComboBox<Protocol>();
DefaultComboBoxModel<Protocol> model = new DefaultComboBoxModel<>();
for(Protocol protocol : Protocol.RADIO_ID_PROTOCOLS)
{
model.addElement(protocol);
}
mComboProtocol.setModel(model);
mComboProtocol.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
Protocol protocol = (Protocol)mComboProtocol.getSelectedItem();
updateEditor(protocol);
setModified(true);
}
});
add(mComboProtocol);
add(new JLabel("Min:"));
mMinRadioField = new JFormattedTextField(mMaskFormatter);
mMinRadioField.getDocument().addDocumentListener(this);
add(mMinRadioField, "growx,push");
add(new JLabel("Max:"));
mMaxRadioField = new JFormattedTextField(mMaskFormatter);
mMaxRadioField.getDocument().addDocumentListener(this);
add(mMaxRadioField, "growx,push");
JLabel help = new JLabel("Help ...");
help.setForeground(Color.BLUE.brighter());
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
help.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
RadioFormat radioFormat = RadioFormat.get(getCurrentProtocol());
JOptionPane.showMessageDialog(RadioRangeEditor.this,
radioFormat.getValidRangeHelpText(), "Help", JOptionPane.INFORMATION_MESSAGE);
}
});
add(help, "align left");
}
private void updateEditor(Protocol protocol)
{
RadioFormat mask = RadioFormat.get(protocol);
int currentMinValue = 0;
int currentMaxValue = 0;
RadioRange radioRange = getRadioRange();
if(radioRange != null)
{
currentMinValue = radioRange.getMinRadio();
currentMaxValue = radioRange.getMaxRadio();
}
try
{
mMinRadioField.setValue(null);
mMaxRadioField.setValue(null);
mMinRadioField.setToolTipText(mask.getValidRangeHelpText());
mMaxRadioField.setToolTipText(mask.getValidRangeHelpText());
mMaskFormatter.setMask(mask.getMask());
if(mask.getMask().contains("*"))
{
mMaskFormatter.setValidCharacters(VALID_CHARACTERS_FOR_ASTERISK_MASKS);
}
mMinRadioField.setValue(RadioFormatter.format(protocol, currentMinValue));
mMaxRadioField.setValue(RadioFormatter.format(protocol, currentMaxValue));
}
catch(ParseException pe)
{
mLog.error("Error applying radio editor mask to mask formatter [" + mask.getMask() + "]");
}
}
private Protocol getCurrentProtocol()
{
return mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
}
public RadioRange getRadioRange()
{
if(getItem() instanceof RadioRange)
{
return (RadioRange)getItem();
}
return null;
}
@Override
public void setItem(AliasID aliasID)
{
super.setItem(aliasID);
RadioRange radioRange = getRadioRange();
updateEditor(radioRange.getProtocol());
if(radioRange != null)
{
mComboProtocol.getModel().setSelectedItem(radioRange.getProtocol());
String minFormatted = RadioFormatter.format(radioRange.getProtocol(), radioRange.getMinRadio());
mMinRadioField.setValue(minFormatted);
String maxFormatted = RadioFormatter.format(radioRange.getProtocol(), radioRange.getMaxRadio());
mMaxRadioField.setValue(maxFormatted);
}
else
{
mComboProtocol.getModel().setSelectedItem(Protocol.UNKNOWN);
mMinRadioField.setValue(RadioFormatter.format(getCurrentProtocol(), 0));
mMaxRadioField.setValue(RadioFormatter.format(getCurrentProtocol(), 0));
}
setModified(false);
repaint();
}
@Override
public void save()
{
RadioRange radioRange = getRadioRange();
if(radioRange != null)
{
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
if(protocol == null)
{
protocol = Protocol.UNKNOWN;
}
radioRange.setProtocol(protocol);
int minValue = -1;
int maxValue = -1;
try
{
minValue = RadioFormatter.parse(protocol, mMinRadioField.getText());
}
catch(ParseException pe)
{
//ignore ... value is still -1 and outside valid value range
}
try
{
maxValue = RadioFormatter.parse(protocol, mMaxRadioField.getText());
}
catch(ParseException pe)
{
//ignore ... value is still -1 and outside valid value range
}
RadioFormat mask = RadioFormat.get(protocol);
//Check for valid value within range ... notify user but allow value to persist
if(minValue < mask.getMinimumValidValue() || minValue > mask.getMaximumValidValue())
{
String message = "Invalid minimum value [" + mMinRadioField.getText() + "]. " + protocol.name() +
" valid range is [" + mask.getValidRangeDescription() + "]";
JOptionPane.showMessageDialog(RadioRangeEditor.this, message,
"Invalid Radio Value", JOptionPane.ERROR_MESSAGE);
}
else
{
radioRange.setMinRadio(minValue);
}
//Check for valid value within range ... notify user but allow value to persist
if(maxValue < mask.getMinimumValidValue() || maxValue > mask.getMaximumValidValue())
{
String message = "Invalid maximum value [" + mMaxRadioField.getText() + "]. " + protocol.name() +
" valid range is [" + mask.getValidRangeDescription() + "]";
JOptionPane.showMessageDialog(RadioRangeEditor.this, message,
"Invalid Radio Value", JOptionPane.ERROR_MESSAGE);
}
else
{
radioRange.setMaxRadio(maxValue);
}
radioRange.setProtocol(protocol);
}
setModified(false);
}
}

View File

@@ -0,0 +1,56 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.record;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
/**
* Alias identifier to flag an alias for audio recording
*/
public class Record extends AliasID
{
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
@Override
public AliasIDType getType()
{
return AliasIDType.RECORD;
}
@Override
public boolean isValid()
{
return true;
}
@Override
public boolean matches(AliasID id)
{
return false;
}
@Override
public String toString()
{
return "Record Audio";
}
}

View File

@@ -0,0 +1,73 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.record;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class RecordEditor extends DocumentListenerEditor<AliasID>
{
private static final long serialVersionUID = 1L;
private static final String HELP_TEXT = "<html>"
+ "<h3>Record Audio</h3>"
+ "Any audio associated with this alias will be recorded.<br><br>";
public RecordEditor(AliasID aliasID )
{
initGUI();
setItem( aliasID );
}
private void initGUI()
{
setLayout( new MigLayout( "fill,wrap 1", "[]", "[][]" ) );
add( new JLabel( "Record Audio" ), "wrap" );
JLabel help = new JLabel( "Help ..." );
help.setForeground( Color.BLUE.brighter() );
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
help.addMouseListener( new MouseAdapter()
{
@Override
public void mouseClicked( MouseEvent e )
{
JOptionPane.showMessageDialog( RecordEditor.this,
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
}
} );
add( help, "align left" );
}
@Override
public void save()
{
}
}

View File

@@ -0,0 +1,114 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.talkgroup;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.protocol.Protocol;
/**
* Integer talkgroup identifier with protocol.
*/
public class Talkgroup extends AliasID
{
private Protocol mProtocol = Protocol.UNKNOWN;
private int mValue;
public Talkgroup()
{
//No arg JAXB constructor
}
public Talkgroup(Protocol protocol, int value)
{
mProtocol = protocol;
mValue = value;
}
@JacksonXmlProperty(isAttribute = true, localName = "value")
public int getValue()
{
return mValue;
}
public void setValue(int value)
{
mValue = value;
}
@JacksonXmlProperty(isAttribute = true, localName = "protocol")
public Protocol getProtocol()
{
return mProtocol;
}
public void setProtocol(Protocol protocol)
{
mProtocol = protocol;
}
@Override
public boolean isValid()
{
if(mProtocol == null || mProtocol == Protocol.UNKNOWN)
{
return false;
}
TalkgroupFormat talkgroupFormat = TalkgroupFormat.get(mProtocol);
return talkgroupFormat.getMinimumValidValue() <= mValue && mValue <= talkgroupFormat.getMaximumValidValue();
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Talkgroup:").append(TalkgroupFormatter.format(mProtocol, mValue));
sb.append(" Protocol:").append((mProtocol));
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
@Override
public boolean matches(AliasID id)
{
if(id instanceof Talkgroup)
{
Talkgroup tgid = (Talkgroup)id;
return (getProtocol() == tgid.getProtocol()) && (getValue() == tgid.getValue());
}
return false;
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
@Override
public AliasIDType getType()
{
return AliasIDType.TALKGROUP;
}
}

View File

@@ -0,0 +1,233 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.talkgroup;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
import io.github.dsheirer.protocol.Protocol;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.text.MaskFormatter;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.ParseException;
public class TalkgroupEditor extends DocumentListenerEditor<AliasID>
{
private final static Logger mLog = LoggerFactory.getLogger(TalkgroupEditor.class);
private static final long serialVersionUID = 1L;
private static final String VALID_CHARACTERS_FOR_ASTERISK_MASKS = "0123456789 ";
private MaskFormatter mMaskFormatter = new MaskFormatter();
private JComboBox<Protocol> mComboProtocol;
private JFormattedTextField mTalkgroupField;
public TalkgroupEditor(AliasID aliasID)
{
initGUI();
setItem(aliasID);
}
private void initGUI()
{
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
add(new JLabel("Protocol:"));
mComboProtocol = new JComboBox<>();
DefaultComboBoxModel<Protocol> model = new DefaultComboBoxModel<>();
for(Protocol protocol : Protocol.TALKGROUP_PROTOCOLS)
{
model.addElement(protocol);
}
mComboProtocol.setModel(model);
mComboProtocol.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
Protocol protocol = (Protocol)mComboProtocol.getSelectedItem();
updateEditor(protocol);
setModified(true);
}
});
add(mComboProtocol);
add(new JLabel("Value:"));
mTalkgroupField = new JFormattedTextField(mMaskFormatter);
mTalkgroupField.getDocument().addDocumentListener(this);
add(mTalkgroupField, "growx,push");
JLabel help = new JLabel("Help ...");
help.setForeground(Color.BLUE.brighter());
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
help.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
TalkgroupFormat talkgroupFormat = TalkgroupFormat.get(getCurrentProtocol());
JOptionPane.showMessageDialog(TalkgroupEditor.this,
talkgroupFormat.getValidRangeHelpText(), "Help", JOptionPane.INFORMATION_MESSAGE);
}
});
add(help, "align left");
}
private void updateEditor(Protocol protocol)
{
TalkgroupFormat mask = TalkgroupFormat.get(protocol);
int currentValue = 0;
Talkgroup talkgroup = getTalkgroup();
if(talkgroup != null)
{
currentValue = talkgroup.getValue();
}
try
{
mTalkgroupField.setValue(null);
mTalkgroupField.setToolTipText(mask.getValidRangeHelpText());
mMaskFormatter.setMask(mask.getMask());
if(mask.getMask().contains("*"))
{
mMaskFormatter.setValidCharacters(VALID_CHARACTERS_FOR_ASTERISK_MASKS);
}
mTalkgroupField.setValue(TalkgroupFormatter.format(protocol, currentValue));
}
catch(ParseException pe)
{
mLog.error("Error applying talkgroup editor mask to mask formatter [" + mask.getMask() + "]");
}
}
private Protocol getCurrentProtocol()
{
return mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
}
/**
* Current talkgroup alias id
*/
public Talkgroup getTalkgroup()
{
if(getItem() instanceof Talkgroup)
{
return (Talkgroup)getItem();
}
return null;
}
@Override
public void setItem(AliasID aliasID)
{
super.setItem(aliasID);
Talkgroup talkgroup = getTalkgroup();
if(talkgroup != null)
{
Protocol currentProtocol = getCurrentProtocol();
if(currentProtocol == talkgroup.getProtocol())
{
updateEditor(talkgroup.getProtocol());
}
mComboProtocol.getModel().setSelectedItem(talkgroup.getProtocol());
String formatted = TalkgroupFormatter.format(talkgroup.getProtocol(), talkgroup.getValue());
mTalkgroupField.setValue(formatted);
}
else
{
mComboProtocol.getModel().setSelectedItem(Protocol.UNKNOWN);
mTalkgroupField.setValue(TalkgroupFormatter.format(getCurrentProtocol(), 0));
}
setModified(false);
repaint();
}
@Override
public void save()
{
Talkgroup talkgroup = getTalkgroup();
if(talkgroup != null)
{
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
if(protocol == null)
{
protocol = Protocol.UNKNOWN;
}
int value = -1;
try
{
value = TalkgroupFormatter.parse(protocol, mTalkgroupField.getText());
}
catch(ParseException pe)
{
//ignore ... value is still -1 and outside valid value range
}
TalkgroupFormat mask = TalkgroupFormat.get(protocol);
//Check for valid value within range ... notify user but allow value to persist
if(value < mask.getMinimumValidValue() || value > mask.getMaximumValidValue())
{
String message = "Invalid value [" + mTalkgroupField.getText() + "]. " + protocol.name() +
" valid range is [" + mask.getValidRangeDescription() + "]";
JOptionPane.showMessageDialog(TalkgroupEditor.this, message, "Invalid Talkgroup Value",
JOptionPane.ERROR_MESSAGE);
}
else
{
talkgroup.setValue(value);
}
talkgroup.setProtocol(protocol);
}
setModified(false);
}
}

View File

@@ -0,0 +1,139 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.alias.id.talkgroup;
import io.github.dsheirer.protocol.Protocol;
/**
* Talkgroup protocol identifier entry masks and min/max valid ranges and valid range descriptions
*
* Note: the getMask() value is intended for use with a MaskFormatter attached to a JFormattedTextField. The
* protocol masks that have a dash (-) use the # (any digit) character and the protocol masks that do not have
* a formatting dash use an asterisk (*). Within the editor(s) that use this enumeration, the editor must use
* a setValidCharacters() on the mask formatter whenever it detects the mask containing one or more asterisks.
*/
public enum TalkgroupFormat
{
APCO25("********", 0, 0xFFFFFF, "0 to 65,535",
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
FLEETSYNC("###-####", 1, 0x7FFFFF, "001-0001 to 127-8192",
"<html>Fleetsync valid ranges are 1-127(prefix)<br>and 1-8192(ident) (ie. 001-0001 to 127-8192)"),
LTR("##-###", 257, 5375, "01-001 to 20-255",
"<html>LTR valid ranges are 1-20(repeater) and 1-255(talkgroup) (ie. 01-001 to 20-255)"),
MDC1200("*****", 1, 0xFFFF, "1 to 65,535",
"MDC-1200 valid value range is 1-65,535"),
MPT1327("###-####", 1, 0x7FFFFF, "000-0001 to 127-8192",
"<html>MPT-1327 valid ranges are 0-127(prefix)<br>and 1-8192(ident) (ie. 000-0001 to 127-8192)"),
PASSPORT("*****", 1, 0xFFFF, "1 to 65,535",
"Passport valid value range is 1-65,535"),
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
"Unknown protocol valid value range is 1-16,777,215");
private String mMask;
private int mMinimumValue;
private int mMaximumValue;
private String mValidRangeDescription;
private String mValidRangeHelpText;
TalkgroupFormat(String mask, int minimumValue, int maximumValue, String validRangeDescription, String validRangeHelpText)
{
mMask = mask;
mMinimumValue = minimumValue;
mMaximumValue = maximumValue;
mValidRangeDescription = validRangeDescription;
mValidRangeHelpText = validRangeHelpText;
}
public String getMask()
{
return mMask;
}
/**
* Minimum valid value for the protocol
*/
public int getMinimumValidValue()
{
return mMinimumValue;
}
/**
* Maximum valid value for the protocol
*/
public int getMaximumValidValue()
{
return mMaximumValue;
}
/**
* Indicates if the value is valid for the protocol
*/
public boolean isValid(int value)
{
return getMinimumValidValue() <= value && value <= getMaximumValidValue();
}
/**
* Short description of the valid value range
*/
public String getValidRangeDescription()
{
return mValidRangeDescription;
}
/**
* Help text for valid value ranges
*/
public String getValidRangeHelpText()
{
return mValidRangeHelpText;
}
public static TalkgroupFormat get(Protocol protocol)
{
if(protocol == null)
{
return UNKNOWN;
}
switch(protocol)
{
case APCO25:
return APCO25;
case FLEETSYNC:
return FLEETSYNC;
case LTR:
case LTR_NET:
case LTR_STANDARD:
return LTR;
case MDC1200:
return MDC1200;
case MPT1327:
return MPT1327;
case PASSPORT:
return PASSPORT;
default:
return UNKNOWN;
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.talkgroup;
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.IntegerFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
/**
* Protocol-specific formatting of talkgroup values and parsing of formatted talkgroup values.
*/
public class TalkgroupFormatter
{
private final static Logger mLog = LoggerFactory.getLogger(TalkgroupFormatter.class);
private static Map<Protocol,IntegerFormatter> mFormatterMap = new HashMap<>();
static
{
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
mFormatterMap.put(Protocol.FLEETSYNC, new FleetsyncTalkgroupFormatter());
LTRTalkgroupFormatter ltr = new LTRTalkgroupFormatter();
mFormatterMap.put(Protocol.LTR, ltr);
mFormatterMap.put(Protocol.LTR_NET, ltr);
mFormatterMap.put(Protocol.LTR_STANDARD, ltr);
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new IntegerFormatter());
}
public TalkgroupFormatter()
{
}
/**
* Parses the formatted value into an integer using a protocol specific formatter
*/
public static int parse(Protocol protocol, String value) throws ParseException
{
IntegerFormatter formatter = mFormatterMap.get(protocol);
if(formatter == null)
{
formatter = mFormatterMap.get(Protocol.UNKNOWN);
}
return formatter.parse(value);
}
/**
* Formats the integer value using a protocol-specific formatter
*/
public static String format(Protocol protocol, int value)
{
IntegerFormatter formatter = mFormatterMap.get(protocol);
if(formatter == null)
{
formatter = mFormatterMap.get(Protocol.UNKNOWN);
}
return formatter.format(value);
}
}

View File

@@ -1,116 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.id.talkgroup;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class TalkgroupIDEditor extends DocumentListenerEditor<AliasID>
{
private static final long serialVersionUID = 1L;
private static final String HELP_TEXT =
"<html><h3>Talkgroup Identifier</h3>"
+ "<b>P25:</b> 4 or 6 hex characters (e.g. <u>AB12</u> or <u>ABC123</u>)<br>"
+ "<b>LTR:</b> A-HH-TTT where A=Area H=Home T=Talkgroup (<u>0-01-128</u>)<br>"
+ "<b>Passport</b>: 5-digit number (<u>12345</u> or <u>00023</u>)<br>"
+ "<br>"
+ "<b>Wildcard:</b> use an asterisk (*) in place of each talkgroup digit (e.g. <u>0*1*5</u>)"
+ "</html>";
private JTextField mTextField;
public TalkgroupIDEditor( AliasID aliasID )
{
initGUI();
setItem( aliasID );
}
private void initGUI()
{
setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][]" ) );
add( new JLabel( "Talkgroup:" ) );
mTextField = new JTextField();
mTextField.getDocument().addDocumentListener( this );
mTextField.setToolTipText( HELP_TEXT );
add( mTextField, "growx,push" );
JLabel help = new JLabel( "Help ..." );
help.setForeground( Color.BLUE.brighter() );
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
help.addMouseListener( new MouseAdapter()
{
@Override
public void mouseClicked( MouseEvent e )
{
JOptionPane.showMessageDialog( TalkgroupIDEditor.this,
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
}
} );
add( help, "align left" );
}
public TalkgroupID getTalkgroupID()
{
if( getItem() instanceof TalkgroupID )
{
return (TalkgroupID)getItem();
}
return null;
}
@Override
public void setItem( AliasID aliasID )
{
super.setItem( aliasID );
TalkgroupID talkgroup = getTalkgroupID();
if( talkgroup != null )
{
mTextField.setText( talkgroup.getTalkgroup() );
}
setModified( false );
repaint();
}
@Override
public void save()
{
TalkgroupID talkgroup = getTalkgroupID();
if( talkgroup != null )
{
talkgroup.setTalkgroup( mTextField.getText() );
}
setModified( false );
}
}

View File

@@ -0,0 +1,164 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.talkgroup;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.protocol.Protocol;
/**
* Integer talkgroup identifier range of values with protocol.
*/
public class TalkgroupRange extends AliasID
{
private Protocol mProtocol = Protocol.UNKNOWN;
private int mMinTalkgroup;
private int mMaxTalkgroup;
public TalkgroupRange()
{
//No arg JAXB constructor
}
/**
* Creates a talkgroup range of from - to talkgroup values (inclusive) for the specified protocol
* @param protocol for the talkgroup range
* @param minTalkgroup starting or minimum talkgroup value
* @param maxTalkgroup ending or maximum talkgroup value
*/
public TalkgroupRange(Protocol protocol, int minTalkgroup, int maxTalkgroup)
{
mProtocol = protocol;
mMinTalkgroup = minTalkgroup;
mMaxTalkgroup = maxTalkgroup;
}
@JacksonXmlProperty(isAttribute = true, localName = "min")
public int getMinTalkgroup()
{
return mMinTalkgroup;
}
public void setMinTalkgroup(int minTalkgroup)
{
mMinTalkgroup = minTalkgroup;
}
@JacksonXmlProperty(isAttribute = true, localName = "max")
public int getMaxTalkgroup()
{
return mMaxTalkgroup;
}
public void setMaxTalkgroup(int maxTalkgroup)
{
mMaxTalkgroup = maxTalkgroup;
}
@JacksonXmlProperty(isAttribute = true, localName = "protocol")
public Protocol getProtocol()
{
return mProtocol;
}
public void setProtocol(Protocol protocol)
{
mProtocol = protocol;
}
@Override
public boolean isValid()
{
if(mProtocol == null || mProtocol == Protocol.UNKNOWN)
{
return false;
}
TalkgroupFormat talkgroupFormat = TalkgroupFormat.get(mProtocol);
return talkgroupFormat.getMinimumValidValue() <= mMinTalkgroup &&
mMinTalkgroup <= talkgroupFormat.getMaximumValidValue() &&
talkgroupFormat.getMinimumValidValue() <= mMaxTalkgroup &&
mMaxTalkgroup <= talkgroupFormat.getMaximumValidValue();
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Talkgroup Range:").append(TalkgroupFormatter.format(mProtocol, mMinTalkgroup));
sb.append(" to ").append(TalkgroupFormatter.format(mProtocol, mMaxTalkgroup));
sb.append(" Protocol:").append(mProtocol != null ? mProtocol : "unspecified");
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
@Override
public boolean matches(AliasID id)
{
if(id instanceof TalkgroupRange)
{
TalkgroupRange tgid = (TalkgroupRange)id;
return (getProtocol() == tgid.getProtocol()) &&
(getMinTalkgroup() == tgid.getMinTalkgroup()) &&
(getMaxTalkgroup() == tgid.getMaxTalkgroup());
}
return false;
}
/**
* Indicates if this talkgroup range contains/includes the talkgroup value
* @param talkgroupValue
* @return
*/
public boolean contains(int talkgroupValue)
{
return getMinTalkgroup() <= talkgroupValue && talkgroupValue <= getMaxTalkgroup();
}
/**
* Indicates if this talkgroup range overlaps the talkgroup range argument.
* @param talkgroupRange to check for overlap
* @return true if the ranges overlap
*/
public boolean overlaps(TalkgroupRange talkgroupRange)
{
return contains(talkgroupRange.getMinTalkgroup()) ||
contains(talkgroupRange.getMaxTalkgroup()) ||
(getMinTalkgroup() < talkgroupRange.getMinTalkgroup() &&
talkgroupRange.getMaxTalkgroup() < getMaxTalkgroup()) ||
(talkgroupRange.getMinTalkgroup() < getMinTalkgroup() &&
getMaxTalkgroup() < talkgroupRange.getMaxTalkgroup());
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
@Override
public AliasIDType getType()
{
return AliasIDType.TALKGROUP_RANGE;
}
}

View File

@@ -0,0 +1,271 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias.id.talkgroup;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
import io.github.dsheirer.protocol.Protocol;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.text.MaskFormatter;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.ParseException;
public class TalkgroupRangeEditor extends DocumentListenerEditor<AliasID>
{
private final static Logger mLog = LoggerFactory.getLogger(TalkgroupRangeEditor.class);
private static final long serialVersionUID = 1L;
private static final String VALID_CHARACTERS_FOR_ASTERISK_MASKS = "0123456789 ";
private MaskFormatter mMaskFormatter = new MaskFormatter();
private JComboBox<Protocol> mComboProtocol;
private JFormattedTextField mMinTalkgroupField;
private JFormattedTextField mMaxTalkgroupField;
public TalkgroupRangeEditor(AliasID aliasID)
{
initGUI();
setItem(aliasID);
}
private void initGUI()
{
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
add(new JLabel("Protocol:"));
mComboProtocol = new JComboBox<Protocol>();
DefaultComboBoxModel<Protocol> model = new DefaultComboBoxModel<>();
for(Protocol protocol : Protocol.TALKGROUP_PROTOCOLS)
{
model.addElement(protocol);
}
mComboProtocol.setModel(model);
mComboProtocol.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
Protocol protocol = (Protocol)mComboProtocol.getSelectedItem();
updateEditor(protocol);
setModified(true);
}
});
add(mComboProtocol);
add(new JLabel("Min:"));
mMinTalkgroupField = new JFormattedTextField(mMaskFormatter);
mMinTalkgroupField.getDocument().addDocumentListener(this);
add(mMinTalkgroupField, "growx,push");
add(new JLabel("Max:"));
mMaxTalkgroupField = new JFormattedTextField(mMaskFormatter);
mMaxTalkgroupField.getDocument().addDocumentListener(this);
add(mMaxTalkgroupField, "growx,push");
JLabel help = new JLabel("Help ...");
help.setForeground(Color.BLUE.brighter());
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
help.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
TalkgroupFormat talkgroupFormat = TalkgroupFormat.get(getCurrentProtocol());
JOptionPane.showMessageDialog(TalkgroupRangeEditor.this,
talkgroupFormat.getValidRangeHelpText(), "Help", JOptionPane.INFORMATION_MESSAGE);
}
});
add(help, "align left");
}
private void updateEditor(Protocol protocol)
{
TalkgroupFormat mask = TalkgroupFormat.get(protocol);
int currentMinValue = 0;
int currentMaxValue = 0;
TalkgroupRange talkgroupRange = getTalkgroupRange();
if(talkgroupRange != null)
{
currentMinValue = talkgroupRange.getMinTalkgroup();
currentMaxValue = talkgroupRange.getMaxTalkgroup();
}
try
{
mMinTalkgroupField.setValue(null);
mMaxTalkgroupField.setValue(null);
mMinTalkgroupField.setToolTipText(mask.getValidRangeHelpText());
mMaxTalkgroupField.setToolTipText(mask.getValidRangeHelpText());
mMaskFormatter.setMask(mask.getMask());
if(mask.getMask().contains("*"))
{
mMaskFormatter.setValidCharacters(VALID_CHARACTERS_FOR_ASTERISK_MASKS);
}
mMinTalkgroupField.setValue(TalkgroupFormatter.format(protocol, currentMinValue));
mMaxTalkgroupField.setValue(TalkgroupFormatter.format(protocol, currentMaxValue));
}
catch(ParseException pe)
{
mLog.error("Error applying talkgroup editor mask to mask formatter [" + mask.getMask() + "]");
}
}
private Protocol getCurrentProtocol()
{
return mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
}
public TalkgroupRange getTalkgroupRange()
{
if(getItem() instanceof TalkgroupRange)
{
return (TalkgroupRange)getItem();
}
return null;
}
@Override
public void setItem(AliasID aliasID)
{
super.setItem(aliasID);
TalkgroupRange talkgroupRange = getTalkgroupRange();
updateEditor(talkgroupRange.getProtocol());
if(talkgroupRange != null)
{
mComboProtocol.getModel().setSelectedItem(talkgroupRange.getProtocol());
String minFormatted = TalkgroupFormatter.format(talkgroupRange.getProtocol(), talkgroupRange.getMinTalkgroup());
mMinTalkgroupField.setValue(minFormatted);
String maxFormatted = TalkgroupFormatter.format(talkgroupRange.getProtocol(), talkgroupRange.getMaxTalkgroup());
mMaxTalkgroupField.setValue(maxFormatted);
}
else
{
mComboProtocol.getModel().setSelectedItem(Protocol.UNKNOWN);
mMinTalkgroupField.setValue(TalkgroupFormatter.format(getCurrentProtocol(), 0));
mMaxTalkgroupField.setValue(TalkgroupFormatter.format(getCurrentProtocol(), 0));
}
setModified(false);
repaint();
}
@Override
public void save()
{
TalkgroupRange talkgroupRange = getTalkgroupRange();
if(talkgroupRange != null)
{
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
if(protocol == null)
{
protocol = Protocol.UNKNOWN;
}
talkgroupRange.setProtocol(protocol);
int minValue = -1;
int maxValue = -1;
try
{
minValue = TalkgroupFormatter.parse(protocol, mMinTalkgroupField.getText());
}
catch(ParseException pe)
{
//ignore ... value is still -1 and outside valid value range
}
try
{
maxValue = TalkgroupFormatter.parse(protocol, mMaxTalkgroupField.getText());
}
catch(ParseException pe)
{
//ignore ... value is still -1 and outside valid value range
}
TalkgroupFormat mask = TalkgroupFormat.get(protocol);
//Check for valid value within range ... notify user but allow value to persist
if(minValue < mask.getMinimumValidValue() || minValue > mask.getMaximumValidValue())
{
String message = "Invalid minimum value [" + mMinTalkgroupField.getText() + "]. " + protocol.name() +
" valid range is [" + mask.getValidRangeDescription() + "]";
JOptionPane.showMessageDialog(TalkgroupRangeEditor.this, message,
"Invalid Talkgroup Value", JOptionPane.ERROR_MESSAGE);
}
else
{
talkgroupRange.setMinTalkgroup(minValue);
}
//Check for valid value within range ... notify user but allow value to persist
if(maxValue < mask.getMinimumValidValue() || maxValue > mask.getMaximumValidValue())
{
String message = "Invalid maximum value [" + mMaxTalkgroupField.getText() + "]. " + protocol.name() +
" valid range is [" + mask.getValidRangeDescription() + "]";
JOptionPane.showMessageDialog(TalkgroupRangeEditor.this, message,
"Invalid Talkgroup Value", JOptionPane.ERROR_MESSAGE);
}
else
{
talkgroupRange.setMaxTalkgroup(maxValue);
}
talkgroupRange.setProtocol(protocol);
}
setModified(false);
}
}

View File

@@ -0,0 +1,187 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.identifier.IdentifierUpdateListener;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base audio module implementation.
*/
public abstract class AbstractAudioModule extends Module implements IAudioSegmentProvider, IdentifierUpdateListener
{
private final static Logger mLog = LoggerFactory.getLogger(AbstractAudioModule.class);
private static final int MAX_SEGMENT_AUDIO_SAMPLE_LENGTH = 8000 * 60 * 1; //8 kHz - 1 minute
private Listener<AudioSegment> mAudioSegmentListener;
protected MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
private Broadcaster<IdentifierUpdateNotification> mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
private AliasList mAliasList;
private AudioSegment mAudioSegment;
private int mAudioSampleCount = 0;
private boolean mRecordAudioOverride;
/**
* Constructs an abstract audio module
*/
public AbstractAudioModule(AliasList aliasList)
{
mAliasList = aliasList;
mIdentifierUpdateNotificationBroadcaster.addListener(mIdentifierCollection);
}
protected abstract int getTimeslot();
/**
* Closes the current audio segment
*/
protected void closeAudioSegment()
{
synchronized(this)
{
if(mAudioSegment != null)
{
mAudioSegment.completeProperty().set(true);
mIdentifierUpdateNotificationBroadcaster.removeListener(mAudioSegment);
mAudioSegment = null;
}
}
}
@Override
public void stop()
{
closeAudioSegment();
}
/**
* Gets the current audio segment, or creates a new audio segment as necessary and broadcasts it to any registered
* listener(s).
*/
protected AudioSegment getAudioSegment()
{
synchronized(this)
{
if(mAudioSegment == null)
{
mAudioSegment = new AudioSegment(mAliasList, getTimeslot());
mAudioSegment.addIdentifiers(mIdentifierCollection.getIdentifiers());
mIdentifierUpdateNotificationBroadcaster.addListener(mAudioSegment);
if(mRecordAudioOverride)
{
mAudioSegment.recordAudioProperty().set(true);
}
if(mAudioSegmentListener != null)
{
mAudioSegment.incrementConsumerCount();
mAudioSegmentListener.receive(mAudioSegment);
}
mAudioSampleCount = 0;
}
return mAudioSegment;
}
}
protected void addAudio(float[] audioBuffer)
{
AudioSegment audioSegment = getAudioSegment();
//If the current segment exceeds the max samples length, close it so that a new segment gets generated
//and then link the segments together
if(mAudioSampleCount >= MAX_SEGMENT_AUDIO_SAMPLE_LENGTH)
{
AudioSegment previous = getAudioSegment();
closeAudioSegment();
audioSegment = getAudioSegment();
audioSegment.linkTo(previous);
}
audioSegment.addAudio(audioBuffer);
mAudioSampleCount += audioBuffer.length;
}
/**
* Sets all audio segments as recordable when the argument is true. Otherwise, defers to the aliased identifiers
* from the identifier collection to determine whether to record the audio or not.
* @param recordAudio set to true to mark all audio as recordable.
*/
public void setRecordAudio(boolean recordAudio)
{
mRecordAudioOverride = recordAudio;
if(mRecordAudioOverride)
{
synchronized(this)
{
if(mAudioSegment != null)
{
mAudioSegment.recordAudioProperty().set(true);
}
}
}
}
/**
* Receive updated identifiers from decoder state(s).
*/
@Override
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
{
return mIdentifierUpdateNotificationBroadcaster;
}
/**
* Identifier collection containing the current set of identifiers received from the decoder state(s).
*/
public MutableIdentifierCollection getIdentifierCollection()
{
return mIdentifierCollection;
}
/**
* Registers an audio segment listener to receive the output from this audio module.
*/
@Override
public void setAudioSegmentListener(Listener<AudioSegment> listener)
{
mAudioSegmentListener = listener;
}
/**
* Unregisters the audio segment listener from receiving audio segments from this module.
*/
@Override
public void removeAudioSegmentListener()
{
mAudioSegmentListener = null;
}
}

View File

@@ -1,252 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.audio;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.audio.output.AudioOutput;
import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.settings.ColorSetting;
import io.github.dsheirer.settings.Setting;
import io.github.dsheirer.settings.SettingChangeListener;
import io.github.dsheirer.settings.SettingsManager;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.awt.*;
public class AudioChannelPanel extends JPanel
implements Listener<AudioEvent>, SettingChangeListener
{
private static final long serialVersionUID = 1L;
private static final Logger mLog = LoggerFactory.getLogger(AudioChannelPanel.class);
public static final String PROPERTY_PREFIX = "audio.channel.panel.color.";
public static final String PROPERTY_COLOR_BACKGROUND = PROPERTY_PREFIX + "background";
public static final String PROPERTY_COLOR_LABEL = PROPERTY_PREFIX + "label";
public static final String PROPERTY_COLOR_MUTED = PROPERTY_PREFIX + "muted";
public static final String PROPERTY_COLOR_VALUE = PROPERTY_PREFIX + "value";
private Font mFont = new Font(Font.MONOSPACED, Font.PLAIN, 16);
private Color mBackgroundColor;
private Color mLabelColor;
private Color mMutedColor;
private Color mValueColor;
private IconManager mIconManager;
private SettingsManager mSettingsManager;
private AudioOutput mAudioOutput;
private JLabel mMutedLabel = new JLabel("M");
private JLabel mChannelName = new JLabel(" ");
private JLabel mToAlias = new JLabel(" ");
private JLabel mTo = new JLabel("-----");
private boolean mConfigured = false;
public AudioChannelPanel(IconManager iconManager, SettingsManager settingsManager, AudioOutput audioOutput)
{
mIconManager = iconManager;
mSettingsManager = settingsManager;
mSettingsManager.addListener(this);
mAudioOutput = audioOutput;
if(mAudioOutput != null)
{
mAudioOutput.addAudioEventListener(this);
mAudioOutput.setMetadataListener(new AudioMetadataProcessor());
}
mBackgroundColor = SystemProperties.getInstance().get(PROPERTY_COLOR_BACKGROUND, Color.BLACK);
mLabelColor = SystemProperties.getInstance().get(PROPERTY_COLOR_LABEL, Color.LIGHT_GRAY);
mMutedColor = SystemProperties.getInstance().get(PROPERTY_COLOR_MUTED, Color.RED);
mValueColor = SystemProperties.getInstance().get(PROPERTY_COLOR_VALUE, Color.GREEN);
init();
}
private void init()
{
setLayout(new MigLayout("align center center, insets 0 0 0 0",
"[][][align right]0[grow,fill]", ""));
setBackground(mBackgroundColor);
mMutedLabel.setFont(mFont);
mMutedLabel.setForeground(mMutedColor);
mMutedLabel.setVisible(false);
add(mMutedLabel);
mChannelName = new JLabel(mAudioOutput != null ? mAudioOutput.getChannelName() : " ");
mChannelName.setFont(mFont);
mChannelName.setForeground(mLabelColor);
add(mChannelName);
mToAlias.setFont(mFont);
mToAlias.setForeground(mValueColor);
add(mToAlias);
mTo.setFont(mFont);
mTo.setForeground(mValueColor);
add(mTo,"wmin 10lp");
}
@Override
public void receive(final AudioEvent audioEvent)
{
switch(audioEvent.getType())
{
case AUDIO_STOPPED:
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
resetLabels();
}
});
break;
case AUDIO_MUTED:
case AUDIO_UNMUTED:
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
mMutedLabel.setVisible(mAudioOutput.isMuted());
}
});
break;
default:
break;
}
}
/**
* Resets the from and to labels. Note: this does not happen on the swing
* event thread. Only invoke from the swing thread.
*/
private void resetLabels()
{
updateLabel(mTo, null, mToAlias, null);
mConfigured = false;
}
/**
* Updates the alias label with text and icon from the alias. Note: this
* does not occur on the Swing event thread -- wrap any calls to this
* method with an event thread call.
*/
private void updateLabel(JLabel textLabel, String value, JLabel iconLabel, Alias alias)
{
if(value != null)
{
if(alias != null)
{
iconLabel.setIcon(mIconManager.getIcon(alias.getIconName(), 18));
String aliasName = alias.getName();
//Truncate label if length exceeds 33 characters - hack!
if(aliasName.length() > 33)
{
textLabel.setText(aliasName.substring(0,30) + "...");
}
else
{
textLabel.setText(aliasName);
}
}
else
{
iconLabel.setIcon(null);
textLabel.setText(value);
}
}
else
{
iconLabel.setIcon(null);
textLabel.setText("-----");
}
}
/**
* Processes audio metadata to update this panel's display values
*/
public class AudioMetadataProcessor implements Listener<Metadata>
{
@Override
public void receive(final Metadata metadata)
{
if(metadata.isUpdated() || !mConfigured)
{
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
updateLabel(mTo, metadata.getPrimaryAddressTo().getIdentifier(),
mToAlias, metadata.getPrimaryAddressTo().getAlias());
mConfigured = true;
}
});
}
}
}
@Override
public void settingChanged(Setting setting)
{
if(setting instanceof ColorSetting)
{
ColorSetting colorSetting = (ColorSetting) setting;
switch(colorSetting.getColorSettingName())
{
case CHANNEL_STATE_LABEL_DECODER:
if(mTo != null)
{
mTo.setForeground(mLabelColor);
}
if(mToAlias != null)
{
mToAlias.setForeground(mLabelColor);
}
break;
default:
break;
}
}
}
@Override
public void settingDeleted(Setting setting)
{
}
}

View File

@@ -1,567 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.audio;
import io.github.dsheirer.audio.output.AudioOutput;
import io.github.dsheirer.audio.output.MonoAudioOutput;
import io.github.dsheirer.audio.output.StereoAudioOutput;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.mixer.MixerChannel;
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
import io.github.dsheirer.source.mixer.MixerManager;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Mixer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class AudioManager implements Listener<AudioPacket>, IAudioController
{
private static final Logger mLog = LoggerFactory.getLogger(AudioManager.class);
public static final int AUDIO_TIMEOUT = 1000; //1 second
public static final String AUDIO_CHANNELS_PROPERTY = "audio.manager.channels";
public static final String AUDIO_MIXER_PROPERTY = "audio.manager.mixer";
public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
new AudioEvent(AudioEvent.Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null);
public static final AudioEvent CONFIGURATION_CHANGE_COMPLETE =
new AudioEvent(AudioEvent.Type.AUDIO_CONFIGURATION_CHANGE_COMPLETE, null);
private LinkedTransferQueue<AudioPacket> mAudioPacketQueue = new LinkedTransferQueue<>();
private Map<Integer,AudioOutputConnection> mChannelConnectionMap = new HashMap<>();
private List<AudioOutputConnection> mAudioOutputConnections = new ArrayList<>();
private AudioOutputConnection mLowestPriorityConnection;
private int mAvailableConnectionCount;
private Map<String,AudioOutput> mAudioOutputMap = new HashMap<>();
private Broadcaster<AudioEvent> mControllerBroadcaster = new Broadcaster<>();
private ScheduledFuture<?> mProcessingTask;
private MixerManager mMixerManager;
private MixerChannelConfiguration mMixerChannelConfiguration;
/**
* Processes all audio produced by the decoding channels and routes audio
* packets to any combination of outputs based on any alias audio routing
* options specified by the user.
*/
public AudioManager(MixerManager mixerManager)
{
mMixerManager = mixerManager;
loadSettings();
}
/**
* Loads the saved mixer configuration or a default configuration for audio playback.
*/
private void loadSettings()
{
MixerChannelConfiguration configuration = null;
SystemProperties properties = SystemProperties.getInstance();
Mixer defaultMixer = AudioSystem.getMixer(null);
String mixer = properties.get(AUDIO_MIXER_PROPERTY, defaultMixer.getMixerInfo().getName());
String channels = properties.get(AUDIO_CHANNELS_PROPERTY, MixerChannel.MONO.name());
MixerChannelConfiguration[] mixerConfigurations = mMixerManager.getOutputMixers();
for(MixerChannelConfiguration mixerConfig : mixerConfigurations)
{
if(mixerConfig.matches(mixer, channels))
{
configuration = mixerConfig;
}
}
if(configuration == null)
{
configuration = getDefaultConfiguration();
}
try
{
setMixerChannelConfiguration(configuration, false);
}
catch(Exception e)
{
mLog.error("Couldn't set stored audio mixer/channel configuration - using default", e);
try
{
setMixerChannelConfiguration(getDefaultConfiguration());
}
catch(Exception e2)
{
mLog.error("Couldn't set default audio mixer/channel configuration - no audio will be available", e2);
}
}
}
/**
* Creates a default audio playback configuration with a mono audio playback channel.
*/
private MixerChannelConfiguration getDefaultConfiguration()
{
/* Use the system default mixer and mono channel as default startup */
Mixer defaultMixer = AudioSystem.getMixer(null);
return new MixerChannelConfiguration(defaultMixer, MixerChannel.MONO);
}
public void dispose()
{
if(mProcessingTask != null)
{
mProcessingTask.cancel(true);
}
mAudioPacketQueue.clear();
mProcessingTask = null;
mChannelConnectionMap.clear();
for(AudioOutputConnection connection : mAudioOutputConnections)
{
connection.dispose();
}
mAudioOutputConnections.clear();
}
/**
* Primary ingest point for audio produced by all decoding channels, for distribution to audio playback devices.
*/
@Override
public synchronized void receive(AudioPacket packet)
{
mAudioPacketQueue.add(packet);
}
/**
* Checks each audio channel assignment and disconnects any inactive connections
*/
private void disconnectInactiveChannelAssignments()
{
boolean changed = false;
for(AudioOutputConnection connection : mAudioOutputConnections)
{
if(connection.isInactive() && mChannelConnectionMap.containsKey(connection.getChannelMetadataID()))
{
mChannelConnectionMap.remove(connection.getChannelMetadataID());
connection.disconnect();
mAvailableConnectionCount++;
changed = true;
}
}
if(changed)
{
updateLowestPriorityAssignment();
}
}
/**
* Identifies the lowest priority channel connection where the a higher value indicates a lower priority.
*/
private void updateLowestPriorityAssignment()
{
mLowestPriorityConnection = null;
for(AudioOutputConnection connection : mAudioOutputConnections)
{
if(connection.isConnected() &&
(mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() < connection.getPriority()))
{
mLowestPriorityConnection = connection;
}
}
}
/**
* Configures audio playback to use the configuration specified in the entry argument.
*
* @param entry to use in configuring the audio playback setup.
* @throws AudioException if there is an error
*/
@Override
public void setMixerChannelConfiguration(MixerChannelConfiguration entry) throws AudioException
{
setMixerChannelConfiguration(entry, true);
}
/**
* Configures audio playback to use the configuration specified in the entry argument.
*
* @param entry to use in configuring the audio playback setup.
* @param saveSettings to save the audio playback configuration settings in the properties file.
* @throws AudioException if there is an error
*/
public void setMixerChannelConfiguration(MixerChannelConfiguration entry, boolean saveSettings) throws AudioException
{
if(entry != null && (entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO))
{
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_STARTED);
if(mProcessingTask != null)
{
mProcessingTask.cancel(true);
}
disposeCurrentConfiguration();
switch(entry.getMixerChannel())
{
case MONO:
AudioOutput mono = new MonoAudioOutput(entry.getMixer());
mAudioOutputConnections.add(new AudioOutputConnection(mono));
mAvailableConnectionCount++;
mAudioOutputMap.put(mono.getChannelName(), mono);
break;
case STEREO:
AudioOutput left = new StereoAudioOutput(entry.getMixer(), MixerChannel.LEFT);
mAudioOutputConnections.add(new AudioOutputConnection(left));
mAvailableConnectionCount++;
mAudioOutputMap.put(left.getChannelName(), left);
AudioOutput right = new StereoAudioOutput(entry.getMixer(), MixerChannel.RIGHT);
mAudioOutputConnections.add(new AudioOutputConnection(right));
mAvailableConnectionCount++;
mAudioOutputMap.put(right.getChannelName(), right);
break;
default:
throw new AudioException("Unsupported mixer channel "
+ "configuration: " + entry.getMixerChannel());
}
mProcessingTask = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioPacketProcessor(),
0, 15, TimeUnit.MILLISECONDS);
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_COMPLETE);
if(saveSettings)
{
SystemProperties properties = SystemProperties.getInstance();
properties.set(AUDIO_MIXER_PROPERTY, entry.getMixer().getMixerInfo().getName());
properties.set(AUDIO_CHANNELS_PROPERTY, entry.getMixerChannel().name());
}
}
}
/**
* Clears all channel assignments and terminates all audio outputs in preparation for complete shutdown or change
* to another mixer/channel configuration
*/
private void disposeCurrentConfiguration()
{
mChannelConnectionMap.clear();
for(AudioOutputConnection connection : mAudioOutputConnections)
{
connection.dispose();
}
mAvailableConnectionCount = 0;
mAudioOutputConnections.clear();
mAudioOutputMap.clear();
mLowestPriorityConnection = null;
}
/**
* Current audio playback mixer channel configuration setting.
*/
@Override
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
{
return mMixerChannelConfiguration;
}
/**
* List of audio outputs available for the current mixer channel configuration
*/
@Override
public List<AudioOutput> getAudioOutputs()
{
List<AudioOutput> outputs = new ArrayList<>(mAudioOutputMap.values());
Collections.sort(outputs, new Comparator<AudioOutput>()
{
@Override
public int compare(AudioOutput first, AudioOutput second)
{
return first.getChannelName().compareTo(second.getChannelName());
}
});
return outputs;
}
/**
* Adds an audio event listener to receive audio event notifications.
*/
@Override
public void addControllerListener(Listener<AudioEvent> listener)
{
mControllerBroadcaster.addListener(listener);
}
/**
* Removes an audio event listener from receiving audio event notifications.
*/
@Override
public void removeControllerListener(Listener<AudioEvent> listener)
{
mControllerBroadcaster.removeListener(listener);
}
/**
* Returns an audio output connection for the packet if one is available, or overrides an existing lower priority
* connection. Returns null if no connection is available for the audio packet.
*
* @param audioPacket from a decoding channel source
* @return an audio output connection or null
*/
private AudioOutputConnection getConnection(AudioPacket audioPacket)
{
int channelMetadataID = audioPacket.getMetadata().getMetadataID();
//Use an existing connection
if(mChannelConnectionMap.containsKey(channelMetadataID))
{
return mChannelConnectionMap.get(channelMetadataID);
}
//Connect to an unused, available connection
if(mAvailableConnectionCount > 0)
{
for(AudioOutputConnection connection : mAudioOutputConnections)
{
if(connection.isDisconnected())
{
connection.connect(channelMetadataID, audioPacket.getMetadata().getAudioPriority());
mChannelConnectionMap.put(channelMetadataID, connection);
mAvailableConnectionCount--;
return connection;
}
}
}
//Preempt an existing lower priority connection and connect when this is a higher priority packet
else
{
int priority = audioPacket.getMetadata().getAudioPriority();
AudioOutputConnection connection = mLowestPriorityConnection;
if(connection != null && priority < connection.getPriority())
{
mChannelConnectionMap.remove(connection.getChannelMetadataID());
connection.connect(channelMetadataID, priority);
mChannelConnectionMap.put(channelMetadataID, connection);
return connection;
}
}
return null;
}
public class AudioPacketProcessor implements Runnable
{
@Override
public void run()
{
try
{
disconnectInactiveChannelAssignments();
if(mAudioPacketQueue != null)
{
List<AudioPacket> packets = new ArrayList<AudioPacket>();
mAudioPacketQueue.drainTo(packets);
for(AudioPacket packet : packets)
{
/* Don't process any packet's marked as do not monitor */
if(!packet.getMetadata().isDoNotMonitor() && packet.getType() == AudioPacket.Type.AUDIO)
{
AudioOutputConnection connection = getConnection(packet);
if(connection != null)
{
connection.receive(packet);
}
}
}
}
}
catch(Exception e)
{
mLog.error("Encountered error while processing audio packets", e);
}
}
}
/**
* Audio output connection manages a connection between a source and an audio
* output and maintains current state information about the audio activity
* received form the source.
*/
public class AudioOutputConnection
{
private static final int DISCONNECTED = -1;
private AudioOutput mAudioOutput;
private int mPriority = 0;
private int mChannelMetadataID = DISCONNECTED;
public AudioOutputConnection(AudioOutput audioOutput)
{
mAudioOutput = audioOutput;
}
public void receive(AudioPacket packet)
{
if(packet.hasMetadata() && packet.getMetadata().getMetadataID() == mChannelMetadataID)
{
int priority = packet.getMetadata().getAudioPriority();
if(mPriority != priority)
{
mPriority = priority;
updateLowestPriorityAssignment();
}
if(mAudioOutput != null)
{
mAudioOutput.receive(packet);
}
}
else
{
if(packet.hasMetadata())
{
mLog.error("Received audio packet from channel metadata [" + packet.getMetadata().getMetadataID() +
"] however this assignment is currently connected to metadata [" + mChannelMetadataID + "]");
}
else
{
mLog.error("Received audio packet with no metadata - cannot route audio packet");
}
}
}
/**
* Terminates the audio output and prepares this connection for disposal
*/
public void dispose()
{
mAudioOutput.dispose();
mAudioOutput = null;
}
/**
* Indicates if this assignment is currently disconnected from a channel source
*/
public boolean isDisconnected()
{
return mChannelMetadataID == DISCONNECTED;
}
/**
* Indicates if this assignment is currently connected to a channel source
*/
public boolean isConnected()
{
return !isDisconnected();
}
/**
* Connects this assignment to the indicated source so that audio
* packets from this source can be sent to the audio output
*/
public void connect(int source, int priority)
{
mChannelMetadataID = source;
mPriority = priority;
updateLowestPriorityAssignment();
mAudioOutput.updateTimestamp();
}
/**
* Currently connected source or -1 if disconnected
*/
public int getChannelMetadataID()
{
return mChannelMetadataID;
}
/**
* Disconnects this assignment from the source and prevents any audio
* from being routed to the audio output until another source is assigned
*/
public void disconnect()
{
mChannelMetadataID = DISCONNECTED;
mPriority = 0;
}
/**
* Indicates if audio output is current inactive, meaning that the
* audio output hasn't recently processed any audio packets.
*/
public boolean isInactive()
{
return (mAudioOutput.getLastActivityTimestamp() + AUDIO_TIMEOUT) < System.currentTimeMillis();
}
public int getPriority()
{
return mPriority;
}
}
}

View File

@@ -1,188 +1,156 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
/*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
import io.github.dsheirer.audio.squelch.SquelchState;
import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.dsp.filter.polyphase.PolyphaseFIRDecimatingFilter_RB;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.IFilteredRealBufferListener;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledExecutorService;
/**
* Provides packaging of demodulated audio sample buffers into audio packets for
* broadcast to registered audio packet listeners. Includes audio packet
* metadata in constructed audio packets.
* Provides packaging of demodulated audio sample buffers into audio segments for broadcast to registered listeners.
* Includes audio packet metadata in constructed audio segments.
*
* Incorporates audio squelch state listener to control if audio packets are
* broadcast or ignored.
* Incorporates audio squelch state listener to control if audio packets are broadcast or ignored.
*/
public class AudioModule extends Module implements IAudioPacketProvider, IFilteredRealBufferListener,
ISquelchStateListener, Listener<RealBuffer>
public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IReusableBufferListener,
Listener<ReusableFloatBuffer>
{
protected static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
private static float[] mDecimationCoefficients;
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
private static float[] sHighPassFilterCoefficients;
static
{
//Band-pass filter to both decimate (48kHz to 8kHz) and block 0 - 300 Hz (LTR signalling).
FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
.sampleRate(48000)
.stopFrequency1(150)
.passFrequencyBegin(300)
.passFrequencyEnd(3500)
.stopFrequency2(4000)
.stopRipple(0.0004)
.passRipple(0.008)
FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
.sampleRate(8000)
.stopBandCutoff(200)
.stopBandAmplitude(0.0)
.stopBandRipple(0.025)
.passBandStart(300)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.build();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
try
{
if(!designer.isValid())
{
throw new FilterDesignException("Couldn't design the audio decimation filter");
}
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
mDecimationCoefficients = designer.getImpulseResponse();
if(designer.isValid())
{
sHighPassFilterCoefficients = designer.getImpulseResponse();
}
}
catch(FilterDesignException e)
catch(FilterDesignException fde)
{
mLog.debug("Error designing filter", e);
mLog.error("Filter design error", fde);
}
}
private RealFIRFilter2 mHighPassFilter = new RealFIRFilter2(sHighPassFilterCoefficients);
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private SquelchState mSquelchState = SquelchState.SQUELCH;
private Listener<AudioPacket> mAudioPacketListener;
private PolyphaseFIRDecimatingFilter_RB mAudioDecimationFilter;
private Metadata mMetadata;
public AudioModule(Metadata metadata)
/**
* Creates an Audio Module.
*/
public AudioModule(AliasList aliasList)
{
mMetadata = metadata;
super(aliasList);
}
mAudioDecimationFilter = new PolyphaseFIRDecimatingFilter_RB(mDecimationCoefficients, 6, 2.0f);
mAudioDecimationFilter.setListener(new Listener<RealBuffer>()
{
@Override
public void receive(RealBuffer realBuffer)
{
if(mAudioPacketListener != null)
{
AudioPacket packet = new AudioPacket(realBuffer.getSamples(), mMetadata.copyOf());
mAudioPacketListener.receive(packet);
}
}
});
@Override
protected int getTimeslot()
{
return 0;
}
@Override
public void dispose()
{
removeAudioSegmentListener();
mSquelchStateListener = null;
mAudioPacketListener = null;
}
@Override
public void reset()
{
getIdentifierCollection().clear();
}
@Override
public void start(ScheduledExecutorService executor)
public void start()
{
/* No start operations provided */
}
@Override
public void stop()
{
/* Issue an end audio packet in case a recorder is still rolling */
if(mAudioPacketListener != null)
{
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
}
}
/**
* Processes demodulated audio samples into audio packets with current audio
* metadata and sends to the registered listener
*/
@Override
public void receive(RealBuffer buffer)
{
if(mAudioPacketListener != null && mSquelchState == SquelchState.UNSQUELCH)
{
mAudioDecimationFilter.receive(buffer);
}
}
@Override
public Listener<RealBuffer> getFilteredRealBufferListener()
{
return this;
}
@Override
public void setAudioPacketListener(Listener<AudioPacket> listener)
{
mAudioPacketListener = listener;
}
@Override
public void removeAudioPacketListener()
{
mAudioPacketListener = null;
}
@Override
public Listener<SquelchState> getSquelchStateListener()
public Listener<SquelchStateEvent> getSquelchStateListener()
{
return mSquelchStateListener;
}
@Override
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
if(mSquelchState == SquelchState.UNSQUELCH)
{
ReusableFloatBuffer buffer = mHighPassFilter.filter(reusableFloatBuffer);
addAudio(buffer.getSamples());
buffer.decrementUserCount();
}
else
{
reusableFloatBuffer.decrementUserCount();
}
}
@Override
public Listener<ReusableFloatBuffer> getReusableBufferListener()
{
//Redirect received reusable buffers to the receive(buffer) method
return this;
}
/**
* Wrapper for squelch state listener
*/
public class SquelchStateListener implements Listener<SquelchState>
public class SquelchStateListener implements Listener<SquelchStateEvent>
{
@Override
public void receive(SquelchState state)
public void receive(SquelchStateEvent event)
{
if(state == SquelchState.SQUELCH && mAudioPacketListener != null)
{
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
}
mSquelchState = event.getSquelchState();
mSquelchState = state;
if(mSquelchState == SquelchState.SQUELCH)
{
closeAudioSegment();
}
}
}
}

View File

@@ -1,78 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.audio;
import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.sample.real.RealBuffer;
public class AudioPacket
{
private Type mType;
private RealBuffer mAudioData;
private Metadata mMetadata;
public AudioPacket(Type type, Metadata metadata)
{
mType = type;
mMetadata = metadata;
}
public AudioPacket(float[] audio, Metadata metadata)
{
this(Type.AUDIO, metadata);
mAudioData = new RealBuffer(audio);
}
public boolean hasMetadata()
{
return mMetadata != null;
}
public Metadata getMetadata()
{
return mMetadata;
}
public void setMetadata(Metadata metadata)
{
mMetadata = metadata;
}
public Type getType()
{
return mType;
}
public RealBuffer getAudioBuffer()
{
return mAudioData;
}
public boolean hasAudioBuffer()
{
return mAudioData != null;
}
public enum Type
{
AUDIO,
END;
}
}

View File

@@ -1,55 +0,0 @@
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AudioPacketRateCounter implements Listener<AudioPacket>
{
private final static Logger mLog =
LoggerFactory.getLogger( AudioPacketRateCounter.class );
private Listener<AudioPacket> mListener;
private long mCount;
private long mStartTime = -1;
private String mName;
public AudioPacketRateCounter()
{
mName = "UNNAMED";
}
public AudioPacketRateCounter( String name )
{
mName = name;
}
@Override
public void receive( AudioPacket packet )
{
if( mStartTime == -1 )
{
mStartTime = System.currentTimeMillis();
}
mCount += packet.getAudioBuffer().getSamples().length;
long elapsed = System.currentTimeMillis() - mStartTime;
if( elapsed != 0 )
{
mLog.debug( "[" + mName + "] Audio Rate: " + ( mCount / elapsed ) + " kHz" );
}
if( mListener != null )
{
mListener.receive( packet );
}
}
public void setListener( Listener<AudioPacket> listener )
{
mListener = listener;
}
}

View File

@@ -0,0 +1,436 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.audio;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Audio segment containing all related metadata and a dynamic collection of audio packets. An audio segment can be
* a discrete (ie start/stop) audio event, or it may be a time-constrained portion of an ongoing continuous audio
* broadcast. Since the audio segment is held in memory until all consumers have finished processing the segment,
* producers should constrain the duration of each audio segment to a reasonable duration.
*
* Producers can link time-constrained audio segments from a continous broadcast (e.g. FM radio station) so that
* consumers can identify audio segments that belong to a continous stream. This linkage presents a memory leak
* potential. Therefore, the audio segment uses the consumer counter to trigger an unlinking via the dispose() mehtod.
* Accurate accounting of consumer count via the increment/decrementConsumerCount() methods is essential for good
* memory management.
*
* Producers will add audio buffers and update identifiers throughout the life-cycle of an audio segment. The producer
* will signal the completion of an audio segment by setting the complete property to true. This allows consumers the
* option to process the audio buffers throughout the life-cycle of the segment, or to process all of the buffers once
* the segment is complete.
*/
public class AudioSegment implements Listener<IdentifierUpdateNotification>
{
private final static Logger mLog = LoggerFactory.getLogger(AudioSegment.class);
private BooleanProperty mComplete = new SimpleBooleanProperty(false);
private BooleanProperty mRecordAudio = new SimpleBooleanProperty(false);
private IntegerProperty mMonitorPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
private ObservableSet<BroadcastChannel> mBroadcastChannels = FXCollections.observableSet(new HashSet<>());
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
private Broadcaster<IdentifierUpdateNotification> mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
private List<float[]> mAudioBuffers = new CopyOnWriteArrayList();
private AtomicInteger mConsumerCount = new AtomicInteger();
private AliasList mAliasList;
private long mStartTimestamp = System.currentTimeMillis();
private boolean mDisposing = false;
private AudioSegment mLinkedAudioSegment;
private int mTimeslot;
/**
* Constructs an instance
*
* @param aliasList for accessing aliases associated with identifiers for this audio segment.
*/
public AudioSegment(AliasList aliasList, int timeslot)
{
mAliasList = aliasList;
mTimeslot = timeslot;
mIdentifierCollection.setTimeslot(timeslot);
}
/**
* Timeslot for this audio segment
*/
public int getTimeslot()
{
return mTimeslot;
}
/**
* Creation timestamp for this audio segment
* @return long milliseconds since epoch
*/
public long getStartTimestamp()
{
return mStartTimestamp;
}
/**
* The complete property is used by the audio segment producer to signal that the segment is complete and no
* additional audio or identifiers will be added to the segment.
*
* @return complete property
*/
public BooleanProperty completeProperty()
{
return mComplete;
}
/**
* An observable list of broadcast channels for this audio segment. Broadcast channels are added to this segment
* across the life-cycle of the segment. As each new alias identifier is added to the segment, any broadcast
* channels assigned to the alias are added to this list. The audio segment producer can also add broadcast
* channels to this list.
*
* @return observable set of broadcast channels
*/
public ObservableSet<BroadcastChannel> broadcastChannelsProperty()
{
return mBroadcastChannels;
}
/**
* Set of broadcast channels from identifier associated aliases for this segment.
*/
public Set<BroadcastChannel> getBroadcastChannels()
{
return Collections.unmodifiableSet(mBroadcastChannels);
}
/**
* Indicates if this segment has audio streaming broadcast channels specified.
*/
public boolean hasBroadcastChannels()
{
return !mBroadcastChannels.isEmpty();
}
/**
* Property to signal that this audio segment should be recorded. This property can either be set by the producer
* of the audio segment, or it can be flipped to true by any aliases that are added to this segment that require
* audio associated with the identifier to be recorded.
*
* @return
*/
public BooleanProperty recordAudioProperty()
{
return mRecordAudio;
}
/**
* Audio playback/monitor priority specified by identifier associated aliases for this segment.
*/
public IntegerProperty monitorPriorityProperty()
{
return mMonitorPriority;
}
/**
* Indicates if at least one of the identifier associated aliases for this segments specifies Do Not Monitor.
*/
public boolean isDoNotMonitor()
{
return mMonitorPriority.get() <= Priority.DO_NOT_MONITOR;
}
/**
* Alias list for this audio segment
*/
public AliasList getAliasList()
{
return mAliasList;
}
/**
* Indicates if this audio segment is linked to a preceding audio segment.
*/
public boolean isLinked()
{
return mLinkedAudioSegment != null;
}
/**
* Indicates if this audio segment is linked to the argument audio segment
* @param audioSegment to check for linkage
* @return true if this segment is linked to the argument
*/
public boolean isLinkedTo(AudioSegment audioSegment)
{
return isLinked() && audioSegment != null && mLinkedAudioSegment.equals(audioSegment);
}
/**
* Optional linked audio segment is the audio segment that precedes this audio segment in a continuous audio
* broadcast.
*
* @return linked audio segment or null.
*/
public AudioSegment getLinkedAudioSegment()
{
return mLinkedAudioSegment;
}
/**
* Links this audio segment to a preceeding/previous audio segment
* @param previousAudioSegment to set as the predecessor
*/
public void linkTo(AudioSegment previousAudioSegment)
{
mLinkedAudioSegment = previousAudioSegment;
}
/**
* Immutable identifier collection for this audio segment
*/
public IdentifierCollection getIdentifierCollection()
{
return mIdentifierCollection;
}
/**
* Adds the collection of identifiers to this segment's identifier collection
* @param identifiers to pre-load into this audio segment
*/
void addIdentifiers(Collection<Identifier> identifiers)
{
for(Identifier identifier: identifiers)
{
addIdentifier(identifier);
}
}
/**
* Unmodifiable copy of the list of audio buffers for this segment.
*
* @return list of audio buffers
*/
public List<float[]> getAudioBuffers()
{
return Collections.unmodifiableList(mAudioBuffers);
}
/**
* Count of audio buffers contained in this segment.
*
* Note: audio buffers can be added to an audio segment throughout the segment's life-cycle by the audio producer.
*/
public int getAudioBufferCount()
{
return mAudioBuffers.size();
}
/**
* Gets the audio buffer at the specified index
* @param index of the buffer to fetch
* @return audio buffer
* @throws IllegalArgumentException if requested index is not valid
*/
public float[] getAudioBuffer(int index)
{
if(0 <= index && index < getAudioBufferCount())
{
return mAudioBuffers.get(index);
}
else
{
throw new IllegalArgumentException("Requested audio buffer at index [" + index + "] does not exist");
}
}
/**
* Indicates if this audio segment has one or more audio buffers
*/
public boolean hasAudio()
{
return !mAudioBuffers.isEmpty();
}
/**
* Removes all audio buffers and decrements the user count on each so that the audio buffer can be reclaimed.
*/
private void dispose()
{
mDisposing = true;
mAudioBuffers.clear();
mIdentifierCollection.clear();
mIdentifierUpdateNotificationBroadcaster.clear();
mLinkedAudioSegment = null;
}
/**
* Increments the consumer count to indicate that a consumer is currently processing this segment. When the
* consumer count returns to zero, this indicates that all consumers are finished with the audio segment and the
* resources can be reclaimed.
*
* Consumer count should only be increased by the producer of the audio segment, or if a consumer distributes the
* segment to additional consumers.
*/
public void incrementConsumerCount()
{
mConsumerCount.incrementAndGet();
}
/**
* Decrements the consumer count. Consumers of this audio segment should invoke this method to signal that they
* will no longer need this audio segment. When all consumers are finished with an audio segment, the audio
* segment resources will be reclaimed.
*/
public void decrementConsumerCount()
{
int count = mConsumerCount.decrementAndGet();
if(count <= 0)
{
dispose();
}
}
/**
* Adds an audio buffer to this segment. Note the producer of the audio buffer should increment the user count
* of the buffer prior to adding it to this segment. This segment will decrement the audio buffer user count once
* all consumers of this audio segment have de-registered via the decrementConsumerCount() method.
*
* @param audioBuffer to add to this segment
*/
public void addAudio(float[] audioBuffer)
{
if(audioBuffer == null)
{
throw new IllegalArgumentException("Can't add null audio buffer");
}
if(mDisposing)
{
throw new IllegalStateException("Can't add audio to an audio segment that is being disposed");
}
mAudioBuffers.add(audioBuffer);
}
/**
* Adds a listener to receive identifier update notifications
*/
public void addIdentifierUpdateNotificationListener(Listener<IdentifierUpdateNotification> listener)
{
mIdentifierUpdateNotificationBroadcaster.addListener(listener);
}
/**
* Removes the identifier update listener.
*/
public void removeIdentifierUpdateNotificationListener(Listener<IdentifierUpdateNotification> listener)
{
mIdentifierUpdateNotificationBroadcaster.removeListener(listener);
}
/**
* Adds the identifier to this audio segment and updates record, priority and streaming properties.
*
* Note: identifiers pass to this method must be checked for timeslot match.
*/
public void addIdentifier(Identifier identifier)
{
mIdentifierCollection.update(identifier);
List<Alias> aliases = mAliasList.getAliases(identifier);
for(Alias alias: aliases)
{
if(alias.isRecordable())
{
mRecordAudio.set(true);
}
//Add all broadcast channels for the alias ... let the set handle duplication.
for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels())
{
mBroadcastChannels.add(broadcastChannel);
}
//Only assign a playback priority if it is lower priority than the current setting.
int playbackPriority = alias.getPlaybackPriority();
if(playbackPriority < mMonitorPriority.get())
{
mMonitorPriority.set(playbackPriority);
}
}
}
/**
* Processes identifier update notifications and updates audio segment properties using the associated aliases
* from the alias list.
*
* @param identifierUpdateNotification containing an identifier.
*/
@Override
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
{
//Only process add updates that match this timeslot
if(identifierUpdateNotification.getTimeslot() == getTimeslot())
{
if(identifierUpdateNotification.isAdd() || identifierUpdateNotification.isSilentAdd())
{
addIdentifier(identifierUpdateNotification.getIdentifier());
}
mIdentifierUpdateNotificationBroadcaster.broadcast(identifierUpdateNotification);
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Audio Segment\n Timeslot:").append(getTimeslot()).append("\n");
sb.append(" Start Time:").append(new Date(getStartTimestamp()));
sb.append(" Linked:").append(isLinked()).append("\n");
sb.append(" Recordable:").append(recordAudioProperty().get()).append("\n");
sb.append("Do Not Monitor:").append(isDoNotMonitor()).append("\n");
sb.append(" Stream To:").append(getBroadcastChannels()).append("\n");
sb.append(" Identifiers:\n").append(getIdentifierCollection()).append("\n");
return sb.toString();
}
}

View File

@@ -0,0 +1,52 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.AbstractReusableBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AudioSegmentBroadcaster<T extends AudioSegment> extends Broadcaster<T>
{
private final static Logger mLog = LoggerFactory.getLogger(AudioSegmentBroadcaster.class);
/**
* Increments the consumer count for the reusable complex buffer and then broadcasts the buffer to all registered
* listeners.
*
* The total consumer count is established and applied to the buffer prior to dispatching. If we were to simply
* increment the consumer count prior to sending to each consumer, there is a possibility that the consumer could
* immediately decrement the consumer count and prematurely signal that the buffer is ready for disposal before we
* send the buffer to all consumers.
*/
@Override
public void broadcast(T audioSegment)
{
for(Listener<T> listener : mListeners)
{
audioSegment.incrementConsumerCount();
listener.receive(audioSegment);
}
//Decrement consumer counter for this broadcaster
audioSegment.decrementConsumerCount();
}
}

View File

@@ -1,19 +1,16 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.audio;
@@ -34,16 +31,16 @@ public class AudioUtils
/**
* Converts the audio packets into a byte array of 16-bit, little-endian audio samples
*/
public static byte[] convertTo16BitSamples(List<AudioPacket> audioPackets)
public static byte[] convertTo16BitSamples(List<float[]> buffers)
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try
{
for(AudioPacket packet: audioPackets)
for(float[] audioBuffer: buffers)
{
//Converting from 32-bit floats to signed 16-bit samples
ByteBuffer buffer = ConversionUtils.convertToSigned16BitSamples(packet.getAudioBuffer().getSamples());
ByteBuffer buffer = ConversionUtils.convertToSigned16BitSamples(audioBuffer);
stream.write(buffer.array());
}
}
@@ -54,4 +51,29 @@ public class AudioUtils
return stream.toByteArray();
}
/**
* Converts the audio packets into a byte array of 16-bit, little-endian audio samples
*/
public static byte[] convert(List<float[]> audioBuffers)
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try
{
for(float[] audioBuffer: audioBuffers)
{
//Converting from 32-bit floats to signed 16-bit samples
ByteBuffer buffer = ConversionUtils.convertToSigned16BitSamples(audioBuffer);
stream.write(buffer.array());
}
}
catch(IOException e)
{
mLog.error("Error writing converted PCM bytes to output stream");
}
return stream.toByteArray();
}
}

View File

@@ -1,6 +1,6 @@
package io.github.dsheirer.audio;
import io.github.dsheirer.audio.output.AudioOutput;
import io.github.dsheirer.audio.playback.AudioOutput;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;

View File

@@ -1,8 +0,0 @@
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.Listener;
public interface IAudioPacketListener
{
public Listener<AudioPacket> getAudioPacketListener();
}

View File

@@ -1,9 +0,0 @@
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.Listener;
public interface IAudioPacketProvider
{
public void setAudioPacketListener( Listener<AudioPacket> listener );
public void removeAudioPacketListener();
}

View File

@@ -0,0 +1,33 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.Listener;
/**
* Interface for audio segment listener
*/
public interface IAudioSegmentListener
{
/**
* Get the audio segment listener
*/
Listener<AudioSegment> getAudioSegmentListener();
}

View File

@@ -0,0 +1,39 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.Listener;
/**
* Interface for audio segment provider
*/
public interface IAudioSegmentProvider
{
/**
* Registers the audio segment listener
* @param listener of audio segments
*/
void setAudioSegmentListener(Listener<AudioSegment> listener);
/**
* De-registers the audio segment listener
*/
void removeAudioSegmentListener();
}

View File

@@ -1,59 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.audio;
/**
* Standard voice inversion foldover frequencies
*
* Reference: http://en.wikipedia.org/wiki/Voice_inversion
*/
public enum InversionFrequency
{
HZ_2500( 2500 ),
HZ_2550( 2550 ),
HZ_2600( 2600 ),
HZ_2632( 2632 ),
HZ_2675( 2675 ),
HZ_2718( 2718 ),
HZ_2800( 2800 ),
HZ_2868( 2868 ),
HZ_2950( 2950 ),
HZ_3023( 3023 ),
HZ_3075( 3075 ),
HZ_3107( 3107 ),
HZ_3150( 3150 ),
HZ_3196( 3196 ),
HZ_3333( 3333 ),
HZ_3339( 3339 ),
HZ_3400( 3400 ),
HZ_3496( 3496 ),
HZ_3729( 3729 ),
HZ_4096( 4096 );
private int mFrequency;
private InversionFrequency( int frequency )
{
mFrequency = frequency;
}
public int getFrequency()
{
return mFrequency;
}
}

View File

@@ -1,25 +1,28 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
/*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
*/
package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.audio.convert.ISilenceGenerator;
import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
@@ -99,15 +102,15 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
/**
* Broadcasts the next song's audio metadata prior to streaming the next song.
*
* @param metadata for the next recording that will be streamed
* @param identifierCollection for the next recording that will be streamed
*/
protected void broadcastMetadata(Metadata metadata)
protected void broadcastMetadata(IdentifierCollection identifierCollection)
{
IBroadcastMetadataUpdater metadataUpdater = getMetadataUpdater();
if(metadataUpdater != null)
{
metadataUpdater.update(metadata);
metadataUpdater.update(identifierCollection);
}
}
@@ -201,6 +204,10 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
mAudioRecordingQueue.offer(recording);
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
}
else
{
recording.removePendingReplay();
}
}
/**
@@ -245,7 +252,11 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
{
if(mBroadcastState != state)
{
mLog.info("[" + getStreamName() + "] status: " + state);
if(state == BroadcastState.CONNECTED || state == BroadcastState.DISCONNECTED)
{
mLog.info("[" + getStreamName() + "] status: " + state);
}
mBroadcastState = state;
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
@@ -369,9 +380,9 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
broadcastAudio(mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS));
}
}
catch(Exception e)
catch(Throwable t)
{
mLog.error("Error while processing audio streaming queue", e);
mLog.error("Error while processing audio streaming queue", t);
}
mProcessing.set(false);
@@ -438,7 +449,7 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
if(connected())
{
broadcastMetadata(nextRecording.getMetadata());
broadcastMetadata(nextRecording.getIdentifierCollection());
}
metadataUpdateRequired = false;

View File

@@ -18,35 +18,40 @@
******************************************************************************/
package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.identifier.IdentifierCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.file.Path;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger;
public class AudioRecording implements Comparable<AudioRecording>
{
private final static Logger mLog = LoggerFactory.getLogger(StreamManager.class);
private final static Logger mLog = LoggerFactory.getLogger(AudioRecording.class);
private Path mPath;
private long mStartTime;
private long mRecordingLength;
private AtomicInteger mPendingReplayCount = new AtomicInteger();
private Metadata mMetadata;
private IdentifierCollection mIdentifierCollection;
private Collection<BroadcastChannel> mBroadcastChannels;
/**
* Audio recording that is ready to be streamed
*
* @param path to the audio recording file
* @param metadata associated with the recording
* @param identifierCollection associated with the recording
* @param start time of recording in milliseconds since epoch
* @param recordingLength in milliseconds
*/
public AudioRecording(Path path, Metadata metadata, long start, long recordingLength)
public AudioRecording(Path path, Collection<BroadcastChannel> broadcastChannels,
IdentifierCollection identifierCollection, long start, long recordingLength)
{
mPath = path;
mMetadata = metadata;
mBroadcastChannels = broadcastChannels;
mIdentifierCollection = identifierCollection;
mStartTime = start;
mRecordingLength = recordingLength;
}
@@ -60,11 +65,27 @@ public class AudioRecording implements Comparable<AudioRecording>
}
/**
* Optional audio metadata for the recording.
* Collection of broadcast channels that this recording should be streamed to
*/
public Metadata getMetadata()
public Collection<BroadcastChannel> getBroadcastChannels()
{
return mMetadata;
return mBroadcastChannels;
}
/**
* Optional audio metadata/identifiers for the recording.
*/
public IdentifierCollection getIdentifierCollection()
{
return mIdentifierCollection;
}
/**
* Indicates if this recording contains an optional identifier collection.
*/
public boolean hasIdentifierCollection()
{
return mIdentifierCollection != null;
}
/**

View File

@@ -0,0 +1,210 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.audio.AudioSegment;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.record.AudioSegmentRecorder;
import io.github.dsheirer.record.RecordFormat;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import io.github.dsheirer.util.TimeStamp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Audio streaming manager monitors audio segments through completion and creates temporary streaming recordings on
* disk and enqueues the temporary recording for streaming.
*/
public class AudioStreamingManager implements Listener<AudioSegment>
{
private final static Logger mLog = LoggerFactory.getLogger(AudioStreamingManager.class);
private LinkedTransferQueue<AudioSegment> mNewAudioSegments = new LinkedTransferQueue<>();
private List<AudioSegment> mAudioSegments = new ArrayList<>();
private Listener<AudioRecording> mAudioRecordingListener;
private BroadcastFormat mBroadcastFormat;
private UserPreferences mUserPreferences;
private ScheduledFuture<?> mAudioSegmentProcessorFuture;
private int mNextRecordingNumber = 1;
/**
* Constructs an instance
* @param listener to receive completed audio recordings
* @param broadcastFormat for temporary recordings
* @param userPreferences to manage recording directories
*/
public AudioStreamingManager(Listener<AudioRecording> listener, BroadcastFormat broadcastFormat, UserPreferences userPreferences)
{
mAudioRecordingListener = listener;
mBroadcastFormat = broadcastFormat;
mUserPreferences = userPreferences;
}
/**
* Primary receive method
*/
@Override
public void receive(AudioSegment audioSegment)
{
mNewAudioSegments.add(audioSegment);
}
/**
* Starts the scheduled audio segment processor
*/
public void start()
{
if(mAudioSegmentProcessorFuture == null)
{
mAudioSegmentProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(),
0, 250, TimeUnit.MILLISECONDS);
}
}
/**
* Stops the scheduled audio segment processor
*/
public void stop()
{
if(mAudioSegmentProcessorFuture != null)
{
mAudioSegmentProcessorFuture.cancel(true);
mAudioSegmentProcessorFuture = null;
}
for(AudioSegment audioSegment: mNewAudioSegments)
{
audioSegment.decrementConsumerCount();
}
mNewAudioSegments.clear();
for(AudioSegment audioSegment: mAudioSegments)
{
audioSegment.decrementConsumerCount();
}
mAudioSegments.clear();
}
/**
* Main processing method to process audio segments
*/
private void processAudioSegments()
{
mNewAudioSegments.drainTo(mAudioSegments);
Iterator<AudioSegment> it = mAudioSegments.iterator();
AudioSegment audioSegment;
while(it.hasNext())
{
audioSegment = it.next();
if(audioSegment.completeProperty().get())
{
it.remove();
if(mAudioRecordingListener != null && audioSegment.hasBroadcastChannels())
{
Path path = getTemporaryRecordingPath();
long length = 0;
for(float[] audioBuffer: audioSegment.getAudioBuffers())
{
length += audioBuffer.length;
}
length /= 8; //Sample rate is 8000 samples per second, or 8 samples per millisecond.
try
{
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3);
IdentifierCollection identifierCollectionCopy =
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
AudioRecording audioRecording = new AudioRecording(path, audioSegment.getBroadcastChannels(),
identifierCollectionCopy, audioSegment.getStartTimestamp(), length);
mAudioRecordingListener.receive(audioRecording);
}
catch(IOException ioe)
{
mLog.error("Error recording temporary stream MP3");
}
}
audioSegment.decrementConsumerCount();
}
}
}
/**
* Creates a temporary streaming recording file path
*/
private Path getTemporaryRecordingPath()
{
StringBuilder sb = new StringBuilder();
sb.append(BroadcastModel.TEMPORARY_STREAM_FILE_SUFFIX);
//Check for integer overflow and readjust negative value to 0
if(mNextRecordingNumber < 0)
{
mNextRecordingNumber = 1;
}
int recordingNumber = mNextRecordingNumber++;
sb.append(recordingNumber).append("_");
sb.append(TimeStamp.getLongTimeStamp("_"));
sb.append(mBroadcastFormat.getFileExtension());
Path temporaryRecordingPath = mUserPreferences.getDirectoryPreference().getDirectoryStreaming().resolve(sb.toString());
return temporaryRecordingPath;
}
/**
* Scheduled runnable to process audio segments.
*/
public class AudioSegmentProcessor implements Runnable
{
@Override
public void run()
{
try
{
processAudioSegments();
}
catch(Throwable t)
{
mLog.error("Error processing audio segments for streaming", t);
}
}
}
}

View File

@@ -1,21 +1,24 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
/*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
*/
package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.alias.AliasModel;
@@ -40,13 +43,10 @@ import io.github.dsheirer.audio.convert.MP3SilenceGenerator;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.gui.editor.EmptyEditor;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.record.AudioRecorder;
import io.github.dsheirer.record.mp3.MP3Recorder;
import io.github.dsheirer.preference.UserPreferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.file.Path;
public class BroadcastFactory
{
private final static Logger mLog = LoggerFactory.getLogger(BroadcastFactory.class);
@@ -60,7 +60,7 @@ public class BroadcastFactory
* @param configuration describing the server and audio types
* @return configured broadcaster or null
*/
public static AudioBroadcaster getBroadcaster(BroadcastConfiguration configuration)
public static AudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel)
{
if(configuration != null)
{
@@ -71,15 +71,15 @@ public class BroadcastFactory
switch(configuration.getBroadcastServerType())
{
case BROADCASTIFY:
return new IcecastTCPAudioBroadcaster((BroadcastifyConfiguration) configuration);
return new IcecastTCPAudioBroadcaster((BroadcastifyConfiguration) configuration, aliasModel);
case ICECAST_TCP:
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration);
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, aliasModel);
case ICECAST_HTTP:
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration);
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, aliasModel);
case SHOUTCAST_V1:
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration);
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, aliasModel);
case SHOUTCAST_V2:
return new ShoutcastV2AudioBroadcaster((ShoutcastV2Configuration) configuration);
return new ShoutcastV2AudioBroadcaster((ShoutcastV2Configuration) configuration, aliasModel);
case UNKNOWN:
default:
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
@@ -147,7 +147,8 @@ public class BroadcastFactory
* @param broadcastModel model for broadcastAudio configurations
* @return an editor for the specified broadcastAudio configuration
*/
public static Editor<BroadcastConfiguration> getEditor(BroadcastConfiguration configuration,
public static Editor<BroadcastConfiguration> getEditor(UserPreferences userPreferences,
BroadcastConfiguration configuration,
BroadcastModel broadcastModel,
AliasModel aliasModel,
IconManager iconManager)
@@ -157,7 +158,7 @@ public class BroadcastFactory
switch(configuration.getBroadcastServerType())
{
case BROADCASTIFY:
editor = new BroadcastifyConfigurationEditor(broadcastModel, aliasModel, iconManager);
editor = new BroadcastifyConfigurationEditor(userPreferences, broadcastModel, aliasModel, iconManager);
break;
case ICECAST_TCP:
editor = new IcecastTCPConfigurationEditor(broadcastModel, aliasModel, iconManager);
@@ -181,21 +182,6 @@ public class BroadcastFactory
return editor;
}
/**
* Creates an audio recorder for the specified broadcastAudio format using the specified path output file name
*/
public static AudioRecorder getAudioRecorder(Path path, BroadcastFormat broadcastFormat)
{
switch(broadcastFormat)
{
case MP3:
return new MP3Recorder(path);
default:
mLog.debug("Unrecognized broadcastAudio format [" + broadcastFormat + "] cannot create audio recorder");
return null;
}
}
public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format)
{
switch(format)

View File

@@ -1,27 +1,30 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
/*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
*/
package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.audio.AudioPacket;
import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
@@ -46,7 +49,7 @@ import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class BroadcastModel extends AbstractTableModel implements Listener<AudioPacket>
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
{
private final static Logger mLog = LoggerFactory.getLogger(BroadcastModel.class);
@@ -71,22 +74,19 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
private Map<String,BroadcastConfiguration> mBroadcastConfigurationMap = new HashMap<>();
private Map<String,AudioBroadcaster> mBroadcasterMap = new HashMap<>();
private IconManager mIconManager;
private StreamManager mStreamManager;
private AliasModel mAliasModel;
private Broadcaster<BroadcastEvent> mBroadcastEventBroadcaster = new Broadcaster<>();
/**
* Model for managing Broadcast configurations and any associated broadcaster instances.
*/
public BroadcastModel(IconManager iconManager)
public BroadcastModel(AliasModel aliasModel, IconManager iconManager, UserPreferences userPreferences)
{
mAliasModel = aliasModel;
mIconManager = iconManager;
mStreamManager = new StreamManager(new CompletedRecordingListener(), BroadcastFormat.MP3,
SystemProperties.getInstance().getApplicationFolder(TEMPORARY_STREAM_DIRECTORY));
mStreamManager.start();
//Monitor to remove temporary recording files that have been streamed by all audio broadcasters
ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecordingDeletionMonitor(),
15l, 15l, TimeUnit.SECONDS);
ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecordingDeletionMonitor(), 15l, 15l, TimeUnit.SECONDS);
removeOrphanedTemporaryRecordings();
}
@@ -276,19 +276,28 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
}
@Override
public void receive(AudioPacket audioPacket)
public void receive(AudioRecording audioRecording)
{
if(audioPacket.hasMetadata() && audioPacket.getMetadata().isStreamable())
if(audioRecording != null && !audioRecording.getBroadcastChannels().isEmpty())
{
for(BroadcastChannel channel: audioPacket.getMetadata().getBroadcastChannels())
for(BroadcastChannel broadcastChannel : audioRecording.getBroadcastChannels())
{
if(mBroadcasterMap.containsKey(channel.getChannelName()))
String channelName = broadcastChannel.getChannelName();
if(channelName != null)
{
mStreamManager.receive(audioPacket);
return;
AudioBroadcaster audioBroadcaster = getBroadcaster(channelName);
if(audioBroadcaster != null)
{
audioRecording.addPendingReplay();
audioBroadcaster.receive(audioRecording);
}
}
}
}
mRecordingQueue.add(audioRecording);
}
/**
@@ -299,23 +308,13 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
if(broadcastConfiguration != null && broadcastConfiguration.isEnabled() && broadcastConfiguration.isValid() &&
!mBroadcasterMap.containsKey(broadcastConfiguration.getName()))
{
AudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration);
AudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration, mAliasModel);
if(audioBroadcaster != null)
{
audioBroadcaster.setListener(new Listener<BroadcastEvent>()
{
@Override
public void receive(BroadcastEvent broadcastEvent)
{
process(broadcastEvent);
}
});
audioBroadcaster.setListener(broadcastEvent -> process(broadcastEvent));
audioBroadcaster.start();
mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getName(), audioBroadcaster);
int index = mBroadcastConfigurations.indexOf(audioBroadcaster.getBroadcastConfiguration());
if(index >= 0)
@@ -580,12 +579,20 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
@Override
public Class<?> getColumnClass(int columnIndex)
{
if(columnIndex == COLUMN_SERVER_ICON)
switch(columnIndex)
{
return ImageIcon.class;
case COLUMN_BROADCASTER_STATUS:
return BroadcastState.class;
case COLUMN_BROADCASTER_AGED_OFF_COUNT:
case COLUMN_BROADCASTER_QUEUE_SIZE:
case COLUMN_BROADCASTER_STREAMED_COUNT:
return Integer.class;
case COLUMN_SERVER_ICON:
return ImageIcon.class;
case COLUMN_STREAM_NAME:
default:
return String.class;
}
return String.class;
}
/**
@@ -694,40 +701,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
}
}
/**
* Processes completed audio recordings and distributes them to the audio broadcasters. Adds the recording to
* the audio recording queue to be monitored for deletion.
*/
public class CompletedRecordingListener implements Listener<AudioRecording>
{
@Override
public void receive(AudioRecording audioRecording)
{
Metadata metadata = audioRecording.getMetadata();
if(metadata != null && metadata.isStreamable())
{
for(BroadcastChannel broadcastChannel : metadata.getBroadcastChannels())
{
String channelName = broadcastChannel.getChannelName();
if(channelName != null)
{
AudioBroadcaster audioBroadcaster = getBroadcaster(channelName);
if(audioBroadcaster != null)
{
audioRecording.addPendingReplay();
audioBroadcaster.receive(audioRecording);
}
}
}
}
mRecordingQueue.add(audioRecording);
}
}
/**
* Monitors the recording queue and removes any recordings that have no pending replays by audio broadcasters
*/

View File

@@ -1,21 +1,24 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
/*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
*/
package io.github.dsheirer.audio.broadcast;
import com.jidesoft.swing.JideSplitPane;
@@ -23,6 +26,7 @@ import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.gui.editor.EmptyEditor;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.preference.UserPreferences;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,6 +55,7 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
private BroadcastModel mBroadcastModel;
private AliasModel mAliasModel;
private IconManager mIconManager;
private UserPreferences mUserPreferences;
private BroadcastStatusPanel mBroadcastStatusPanel;
private JideSplitPane mSplitPane;
@@ -60,12 +65,14 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
private JButton mCopyButton = new JButton(COPY_BROADCAST_CONFIGURATION);
private JButton mDeleteButton = new JButton(DELETE_BROADCAST_CONFIGURATION);
public BroadcastPanel(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager)
public BroadcastPanel(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager,
UserPreferences userPreferences)
{
mBroadcastModel = broadcastModel;
mAliasModel = aliasModel;
mIconManager = iconManager;
mEditor = mEmptyEditor;
mUserPreferences = userPreferences;
init();
}
@@ -74,7 +81,8 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
{
setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]"));
mBroadcastStatusPanel = new BroadcastStatusPanel(mBroadcastModel);
mBroadcastStatusPanel = new BroadcastStatusPanel(mBroadcastModel, mUserPreferences,
"broadcast.panel.broadcast.status.panel");
mBroadcastStatusPanel.getTable().setAutoCreateRowSorter(true);
mBroadcastStatusPanel.getTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@@ -210,8 +218,8 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
if (selectedConfiguration != null)
{
mEditor = BroadcastFactory.getEditor(selectedConfiguration, mBroadcastModel, mAliasModel,
mIconManager);
mEditor = BroadcastFactory.getEditor(mUserPreferences, selectedConfiguration, mBroadcastModel,
mAliasModel, mIconManager);
}
else
{

View File

@@ -70,6 +70,11 @@ public enum BroadcastState
*/
MOUNT_POINT_IN_USE("Mount Point In Use", false),
/**
* Network is unavailable or the server address cannot be resolved
*/
NETWORK_UNAVAILABLE("Network Unavailable", false),
/**
* Server is not known or reachable
*/

View File

@@ -1,6 +1,7 @@
/*******************************************************************************
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,25 +15,37 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* *****************************************************************************
*/
package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.awt.Color;
import java.awt.Component;
public class BroadcastStatusPanel extends JPanel
{
private JTable mTable;
private JTableColumnWidthMonitor mColumnWidthMonitor;
private JScrollPane mScrollPane;
private BroadcastModel mBroadcastModel;
private UserPreferences mUserPreferences;
private String mPreferenceKey;
public BroadcastStatusPanel(BroadcastModel broadcastModel)
public BroadcastStatusPanel(BroadcastModel broadcastModel, UserPreferences userPreferences, String preferenceKey)
{
mBroadcastModel = broadcastModel;
mUserPreferences = userPreferences;
mPreferenceKey = preferenceKey;
init();
}
@@ -52,6 +65,7 @@ public class BroadcastStatusPanel extends JPanel
renderer.setHorizontalAlignment(SwingConstants.CENTER);
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
mScrollPane = new JScrollPane(mTable);
@@ -96,7 +110,8 @@ public class BroadcastStatusPanel extends JPanel
setBackground(table.getBackground());
setForeground(Color.LIGHT_GRAY);
}
else if(state == BroadcastState.INVALID_SETTINGS)
else if(state == BroadcastState.INVALID_SETTINGS ||
state == BroadcastState.NETWORK_UNAVAILABLE)
{
setBackground(Color.YELLOW);
setForeground(table.getForeground());

View File

@@ -18,14 +18,14 @@
******************************************************************************/
package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.identifier.IdentifierCollection;
public interface IBroadcastMetadataUpdater
{
/**
* Updates broadcast audio metadata
*
* @param metadata containing metadata attributes
* @param identifierCollection containing metadata attributes
*/
void update(Metadata metadata);
void update(IdentifierCollection identifierCollection);
}

View File

@@ -1,233 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.audio.AudioPacket;
import io.github.dsheirer.record.AudioRecorder;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import io.github.dsheirer.util.TimeStamp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class StreamManager implements Listener<AudioPacket>
{
private final static Logger mLog = LoggerFactory.getLogger(StreamManager.class);
private static final long MAXIMUM_RECORDER_LIFESPAN_MILLIS = 30000; //30 seconds
private static AtomicInteger sNextRecordingNumber = new AtomicInteger();
private Listener<AudioRecording> mAudioRecordingListener;
private BroadcastFormat mBroadcastFormat;
private Path mTempDirectory;
private Map<Integer,AudioRecorder> mStreamRecorders = new HashMap<>();
private Runnable mRecorderMonitor;
private ScheduledFuture<?> mRecorderMonitorFuture;
private AtomicBoolean mRunning = new AtomicBoolean();
/**
* Stream manager processes all incoming audio packets and reassembles individual audio streams, converts audio
* to desired output format and persists each stream to disc. Each recording is capped at a maximum length to
* ensure that recordings don't run too long before they are streamed out and to ensure that inactive recordings
* are closed in a timely fashion.
*
* Completed streamable audio recordings are nominated to the output listener (for broadcast) upon completion
*
* @param listener to receive completed audio recordings
* @param tempDirectory where to store temporary audio recordings
*/
public StreamManager(Listener<AudioRecording> listener, BroadcastFormat broadcastFormat, Path tempDirectory)
{
assert (tempDirectory != null && Files.isDirectory(tempDirectory));
mAudioRecordingListener = listener;
mBroadcastFormat = broadcastFormat;
mTempDirectory = tempDirectory;
}
/**
* Starts the stream manager. Schedules a processor to monitor for inactive temporary stream recorders
* every 2 seconds.
*/
public void start()
{
if(mRunning.compareAndSet(false, true))
{
if(mRecorderMonitor == null)
{
mRecorderMonitor = new RecorderMonitor();
}
mRecorderMonitorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mRecorderMonitor,
0,2, TimeUnit.SECONDS);
}
}
/**
* Stops the stream manager. Stops the inactive temporary stream recorder monitoring thread.
*/
public void stop()
{
if(mRunning.compareAndSet(true, false))
{
if(mRecorderMonitorFuture != null)
{
mRecorderMonitorFuture.cancel(true);
}
synchronized(mStreamRecorders)
{
List<Integer> streamKeys = new ArrayList<>(mStreamRecorders.keySet());
for(Integer streamKey : streamKeys)
{
removeRecorder(streamKey);
}
}
}
}
@Override
public void receive(AudioPacket audioPacket)
{
if(mRunning.get() && audioPacket.hasMetadata())
{
synchronized(mStreamRecorders)
{
int channelMetadataID = audioPacket.getMetadata().getMetadataID();
AudioPacket.Type type = audioPacket.getType();
if(type == AudioPacket.Type.AUDIO)
{
if(mStreamRecorders.containsKey(channelMetadataID))
{
AudioRecorder recorder = mStreamRecorders.get(channelMetadataID);
if(recorder != null)
{
recorder.receive(audioPacket);
}
}
else
{
AudioRecorder recorder = BroadcastFactory.getAudioRecorder(getTemporaryRecordingPath(),
mBroadcastFormat);
recorder.start(ThreadPool.SCHEDULED);
recorder.receive(audioPacket);
mStreamRecorders.put(channelMetadataID, recorder);
}
}
else if(type == AudioPacket.Type.END)
{
removeRecorder(channelMetadataID);
}
else
{
mLog.info("Unrecognized Audio Packet Type: " + type);
}
}
}
}
/**
* Removes the recorder associated with the source channel ID.
*
* Note: this method invocation is not thread safe and must be invoked by a thread safe mechanism that protects the
* mStreamRecorders map.
*
* @param sourceChannelID identifying the recorder
*/
private void removeRecorder(Integer sourceChannelID)
{
if(mStreamRecorders.containsKey(sourceChannelID))
{
AudioRecorder recorder = mStreamRecorders.remove(sourceChannelID);
recorder.close(new Listener<AudioRecorder>()
{
@Override
public void receive(AudioRecorder audioRecorder)
{
AudioRecording audioRecording =
new AudioRecording(audioRecorder.getPath(), audioRecorder.getMetadata(),
audioRecorder.getTimeRecordingStart(), audioRecorder.getRecordingLength());
if(mAudioRecordingListener != null)
{
mAudioRecordingListener.receive(audioRecording);
}
}
});
}
}
/**
* Creates a temporary streaming recording file path
*/
private Path getTemporaryRecordingPath()
{
StringBuilder sb = new StringBuilder();
sb.append(BroadcastModel.TEMPORARY_STREAM_FILE_SUFFIX);
sb.append(sNextRecordingNumber.incrementAndGet()).append("_");
sb.append(TimeStamp.getLongTimeStamp("_"));
sb.append(mBroadcastFormat.getFileExtension());
Path temporaryRecordingPath = mTempDirectory.resolve(sb.toString());
return temporaryRecordingPath;
}
/**
* Monitors recorders to ensure they don't exceed the maximum life-span allowed for a recording.
*/
public class RecorderMonitor implements Runnable
{
@Override
public void run()
{
synchronized(mStreamRecorders)
{
long now = System.currentTimeMillis();
mStreamRecorders.entrySet().stream()
.filter(entry -> entry.getValue().getTimeRecordingStart() + MAXIMUM_RECORDER_LIFESPAN_MILLIS < now)
.forEach(entry ->
{
mLog.info("cycling recorder - max temporary streaming recording time limit reached [" +
entry.getValue().getPath().toString() + "]");
removeRecorder(entry.getKey());
});
}
}
}
}

View File

@@ -1,24 +1,26 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
/*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
*/
package io.github.dsheirer.audio.broadcast.broadcastify;
import com.radioreference.api.soap2.UserFeedBroadcast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastConfigurationEditor;
@@ -26,6 +28,8 @@ import io.github.dsheirer.audio.broadcast.BroadcastEvent;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
import io.github.dsheirer.sample.Listener;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
@@ -64,10 +68,13 @@ public class BroadcastifyConfigurationEditor extends BroadcastConfigurationEdito
private JCheckBox mEnabled;
private JButton mSaveButton;
private JButton mResetButton;
private UserPreferences mUserPreferences;
public BroadcastifyConfigurationEditor(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager)
public BroadcastifyConfigurationEditor(UserPreferences userPreferences, BroadcastModel broadcastModel,
AliasModel aliasModel, IconManager iconManager)
{
super(broadcastModel, aliasModel, iconManager);
mUserPreferences = userPreferences;
init();
}
@@ -91,7 +98,7 @@ public class BroadcastifyConfigurationEditor extends BroadcastConfigurationEdito
@Override
public void actionPerformed(ActionEvent e)
{
final UserFeedSelectionDialog dialog = new UserFeedSelectionDialog(BroadcastifyConfigurationEditor.this);
final UserFeedSelectionDialog dialog = new UserFeedSelectionDialog(mUserPreferences,BroadcastifyConfigurationEditor.this);
EventQueue.invokeLater(new Runnable()
{
@@ -391,7 +398,7 @@ public class BroadcastifyConfigurationEditor extends BroadcastConfigurationEdito
@Override
public void receive(UserFeedBroadcast userFeedBroadcast)
{
mName.setText(userFeedBroadcast.getDescr());
mName.setText(userFeedBroadcast.getDescription());
mServer.setText(userFeedBroadcast.getHostname());
mPort.setText(userFeedBroadcast.getPort());
mMountPoint.setText(userFeedBroadcast.getMount());

View File

@@ -1,78 +1,155 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
/*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
*/
package io.github.dsheirer.audio.broadcast.broadcastify;
import com.radioreference.api.soap2.UserFeedBroadcast;
import io.github.dsheirer.radioreference.RadioReferenceService;
import com.google.common.eventbus.Subscribe;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.RadioReferenceException;
import io.github.dsheirer.rrapi.RadioReferenceService;
import io.github.dsheirer.rrapi.type.AuthorizationInformation;
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
import io.github.dsheirer.rrapi.type.UserInfo;
import io.github.dsheirer.sample.Listener;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.xml.rpc.ServiceException;
import java.awt.*;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.UnknownHostException;
import java.rmi.RemoteException;
import java.util.List;
public class UserFeedSelectionDialog extends JDialog implements DocumentListener
{
private final static Logger mLog = LoggerFactory.getLogger( UserFeedSelectionDialog.class );
public static final String SDRTRUNK_APP_KEY = "88969092";
private Listener<UserFeedBroadcast> mListener;
private UserPreferences mUserPreferences;
private JTextField mUserNameText;
private JTextField mPasswordText;
private JCheckBox mSaveCredentialsCheckBox;
private JButton mClearStoredCredentialsButton;
private JButton mLookupButton;
private JList mUserFeedBroadcastList;
private JButton mOKButton;
public UserFeedSelectionDialog(Listener<UserFeedBroadcast> listener)
public UserFeedSelectionDialog(UserPreferences userPreferences, Listener<UserFeedBroadcast> listener)
{
mUserPreferences = userPreferences;
mListener = listener;
//Register to receive radio reference preferences updates
MyEventBus.getEventBus().register(this);
init();
}
private void init()
{
JPanel panel = new JPanel();
panel.setLayout(new MigLayout("","[align right,grow,fill][align left,grow,fill][]","[][][][][grow][]"));
panel.setLayout(new MigLayout("","[align right,grow,fill][align left,grow,fill][]","[][][][][][grow][]"));
panel.add(new JLabel("Please enter your credentials"), "align left,span");
panel.add(new JLabel("User Name:"));
mUserNameText = new JTextField();
String storedUserName = mUserPreferences.getRadioReferencePreference().getUserName();
if(storedUserName != null)
{
mUserNameText.setText(storedUserName);
}
mUserNameText.getDocument().addDocumentListener(this);
panel.add(mUserNameText, "wrap");
panel.add(new JLabel("Password:"));
mPasswordText = new JTextField();
String storedPassword = mUserPreferences.getRadioReferencePreference().getPassword();
if(storedPassword != null)
{
mPasswordText.setText(storedPassword);
}
mPasswordText.getDocument().addDocumentListener(this);
panel.add(mPasswordText, "wrap");
mSaveCredentialsCheckBox = new JCheckBox("Store Credentials");
mSaveCredentialsCheckBox.setSelected(mUserPreferences.getRadioReferencePreference().isStoreCredentials());
mSaveCredentialsCheckBox.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
//If user unchecks box and there are stored credentials, prompt to clear them
if(!mSaveCredentialsCheckBox.isSelected() && mUserPreferences.getRadioReferencePreference().hasStoredCredentials())
{
int choice = JOptionPane.showOptionDialog(UserFeedSelectionDialog.this,
"Would you like to remove previously stored credentials from this computer?",
"Clear Stored Credentials?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE,
null, null, 0);
if(choice == JOptionPane.YES_OPTION)
{
mUserPreferences.getRadioReferencePreference().removeStoreCredentials();
}
}
}
});
panel.add(mSaveCredentialsCheckBox);
mClearStoredCredentialsButton = new JButton("Remove");
mClearStoredCredentialsButton.setToolTipText("Clear stored credentials");
mClearStoredCredentialsButton.setEnabled(mUserPreferences.getRadioReferencePreference().hasStoredCredentials());
mClearStoredCredentialsButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
mUserPreferences.getRadioReferencePreference().removeStoreCredentials();
}
});
panel.add(mClearStoredCredentialsButton, "wrap");
mLookupButton = new JButton("Get Feeds");
mLookupButton.setEnabled(false);
mLookupButton.setEnabled(storedPassword != null && storedPassword != null);
mLookupButton.addActionListener(new ActionListener()
{
@Override
@@ -87,45 +164,25 @@ public class UserFeedSelectionDialog extends JDialog implements DocumentListener
{
try
{
RadioReferenceService service =
new RadioReferenceService(mUserNameText.getText(), mPasswordText.getText());
AuthorizationInformation authorizationInformation = new AuthorizationInformation(SDRTRUNK_APP_KEY,
mUserNameText.getText(), mPasswordText.getText());
UserFeedBroadcast[] feeds = service.getUserFeedBroadcasts();
mLog.debug("User:" + authorizationInformation.getUserName() + " Pass:" + authorizationInformation.getPassword() + " Key:" + authorizationInformation.getApplicationKey());
mUserFeedBroadcastList.setListData(feeds);
RadioReferenceService service = new RadioReferenceService(authorizationInformation);
UserInfo userInfo = service.getUserInfo();
mLog.info("RR User:" + userInfo.getUserName() + " Expiration Date:" + userInfo.getExpirationDate());
List<UserFeedBroadcast> feeds = service.getUserFeeds();
mUserFeedBroadcastList.setListData(feeds.toArray(new UserFeedBroadcast[feeds.size()]));
}
catch(ServiceException se)
catch(RadioReferenceException rre)
{
JOptionPane.showMessageDialog(UserFeedSelectionDialog.this,
"Unable to create radio reference web service client. See log file.",
"Error", JOptionPane.ERROR_MESSAGE);
mLog.error("Error creating the radio reference web service client", se);
}
catch(RemoteException re)
{
Throwable cause = re.getCause();
if(cause == null)
{
JOptionPane.showMessageDialog(UserFeedSelectionDialog.this,
re.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
else if(cause instanceof UnknownHostException)
{
JOptionPane.showMessageDialog(UserFeedSelectionDialog.this,
"Broadcastify server is unavailable", "No Server",
JOptionPane.ERROR_MESSAGE);
}
else
{
JOptionPane.showMessageDialog(UserFeedSelectionDialog.this,
"An unknown error has occurred. Please see log file.", "" +
"Unknown Error", JOptionPane.ERROR_MESSAGE);
mLog.error("Error retrieving user broadcast feed configurations from radio reference " +
"service: ", re);
}
mLog.error("Error creating the radio reference web service client", rre);
}
mLookupButton.setEnabled(true);
@@ -160,6 +217,7 @@ public class UserFeedSelectionDialog extends JDialog implements DocumentListener
{
if(mListener != null)
{
storeCredentials();
mListener.receive((UserFeedBroadcast)mUserFeedBroadcastList.getSelectedValue());
}
@@ -182,11 +240,37 @@ public class UserFeedSelectionDialog extends JDialog implements DocumentListener
setTitle("Broadcastify Feed Lookup");
setLayout(new BorderLayout());
add(panel, BorderLayout.CENTER);
setSize(300,250);
setSize(400,450);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setModalityType(ModalityType.APPLICATION_MODAL);
}
/**
* Stores login credentials in the user preferences
*/
private void storeCredentials()
{
boolean store = mSaveCredentialsCheckBox.isSelected();
mUserPreferences.getRadioReferencePreference().setStoreCredentials(store);
if(store)
{
String userName = mUserNameText.getText();
if(userName != null)
{
mUserPreferences.getRadioReferencePreference().setUserName(userName);
}
String password = mPasswordText.getText();
if(password != null)
{
mUserPreferences.getRadioReferencePreference().setPassword(password);
}
}
}
private void updateButtons()
{
String username = mUserNameText.getText();
@@ -240,8 +324,20 @@ public class UserFeedSelectionDialog extends JDialog implements DocumentListener
setForeground(list.getForeground());
}
setText(value.getDescr());
setText(value.getDescription());
return this; }
return this;
}
}
@Subscribe
public void preferenceUpdated(PreferenceType preferenceType)
{
if(preferenceType == PreferenceType.RADIO_REFERENCE)
{
//Update the store-credentials checkbox only, so we don't change the username or password currently displayed
mSaveCredentialsCheckBox.setSelected(mUserPreferences.getRadioReferencePreference().isStoreCredentials());
mClearStoredCredentialsButton.setEnabled(mUserPreferences.getRadioReferencePreference().hasStoredCredentials());
}
}
}

View File

@@ -18,6 +18,7 @@
******************************************************************************/
package io.github.dsheirer.audio.broadcast.icecast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.AudioBroadcaster;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
@@ -25,10 +26,12 @@ import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
public abstract class IcecastAudioBroadcaster extends AudioBroadcaster
{
private IBroadcastMetadataUpdater mMetadataUpdater;
private AliasModel mAliasModel;
public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration)
public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, AliasModel aliasModel)
{
super(broadcastConfiguration);
mAliasModel = aliasModel;
}
/**
@@ -44,7 +47,7 @@ public abstract class IcecastAudioBroadcaster extends AudioBroadcaster
{
if(mMetadataUpdater == null)
{
mMetadataUpdater = new IcecastBroadcastMetadataUpdater(getConfiguration());
mMetadataUpdater = new IcecastBroadcastMetadataUpdater(getConfiguration(), mAliasModel);
}
return mMetadataUpdater;

Some files were not shown because too many files have changed in this diff Show More