Very cool![]()
Just wanted to tell you that this project is amazing. Really love what you've done there.
So instantly forked this and also did a pull request for swift 4 conversion.
Keep up this great work!
Thanks very much for the great feedback. Glad you like it. It's messages like yours that give me the motivation to keep this project going.
I'm relatively new to GitHub, so I don't understand what exactly a pull request is. I did read up on it, and I'm assuming it just means that you're going to be working on it separately ? It's more of a notification than a request, right ?
Wow, the UI is not very Mac'ish. Looks like Winamp from the 90s.![]()
Hey, I'm also quite new to this stuff. Actually it's my first pull request I ever did, but I also read about how git and stuff works.
Basically it's a request to merge changes into the origin. In this case you have the ability to review all those changes and decide on each whether to commit it to your own repo or not. So instead of you committing your own changes it's like you committing someone elses changes with the ability to review them beforehand (and do stuff like follow up commits). Please someone correct me if I'm wrong.
Part of me thinks you should have one codebase but another thinks you should keep them separate, as different people maintaining the different code bases could leak to features on one version that wouldn’t be on the other.
Hey, I'm also quite new to this stuff. Actually it's my first pull request I ever did, but I also read about how git and stuff works.
Basically it's a request to merge changes into the origin. In this case you have the ability to review all those changes and decide on each whether to commit it to your own repo or not. So instead of you committing your own changes it's like you committing someone elses changes with the ability to review them beforehand (and do stuff like follow up commits). Please someone correct me if I'm wrong.
Hey, did some UI changes on a test branch I made to experiment a bit. Went for a slightly more native feel, while keeping the overall look and feel. Also completely redid autolayout which took like forever, but now UI changes should be easier. Only got like one two things that still don't look and work as intended (for example the EQ Sliders, or the resizing of the main window when hiding the EQ), but other than that it works fine without crashes and all with Swift 4.
View attachment 725794
So, looking for some feedback on this. It has system integrated blurry background instead of normal transparency, which can also be turned of by system preferences.
Regarding different branches in my opinion a single codebase would be best, but this is up to you. New features that only change specific parts of existing code or mostly add new stuff to it can be developed on side-branches and once completely developed and tested to be stable they can easily be merged. This way multiple people can work together on the same product. But after all, this is totally up to you, I'm fine with whatever your suggestion is on that topic.
Here's the original version rendered on my Macbook as requested.
View attachment 725796
Hey, thanks for your detailed answer. Now that you explained why, it makes sense to have Swift 3 and Swift 4 version separate. Still, I will try to keep up with the changes that you're doing in Swift 3 and try to bring them up to Swift 4. I'm an update junkie and always have to have the latest and greatest. For me, having to figure things out on my own is also part of the deal, and it's a thing I really like.
Regarding the window controls I'm just experimenting, they are gone as easy as two clicks, so I didn't really care. But a native close button for the playlist makes total sense in my opinion. How would it make things more complicated, when using native controls? Also it's often quicker in terms of mouse movement when having a dedicated close button at the playlist window itself.
Another thing i maybe want to change in terms of UX is having the playlist resize and docking controls on the main window. Besides that there's more things I just wanna try and play with (like media keys, menu bar icon...), knowing I can always go back to how it was.
Yeah, I'm also very interested in what you're doing on your side. It is already my favorite music player on mac xD and I found out about it not even two days ago. Haven't even really dug deep into the code and already learned so much from your work.
Oh, and just want to point out that somehow your custom minimize button does not work for me. Nothing happens when I press it.
It makes my eyes bleed so I won't use it. But kudos to you! So many times I've been in a situation where I can't find an app for my needs, but unfortunately I never learned how to code.Haha, yes, this project was inspired by WinampIt is it's doppelganger in the Mac world, since there aren't any (or many) decent substitutes.
When I started using Mac, many years ago, my first aim was to find a Winamp substitute that worked just as simply and effectively. 8 years later, having still not found one, I tried to write one![]()
You're a very fast learner !
Yes, I'm very much open to doing things a different way. Feel free to experiment with moving the window controls around as you wish ... as long as they're intuitive to the end user.
What I was trying to say about the playlist window controls was that the 3 custom resize buttons behave differently than the native green maximize control. So, this represents a disparity or inconsistency. Consistency is good from a UX perspective. When the user clicks a button, he/she should know exactly what it is going to do. If the custom maximize button expands the window only vertically and the native control expands it both vertically and horizontally, that is inconsistent behavior ... not a good user experience.
Take a look in WindowViewController (or better yet, just run the app and use the playlist maximize controls) ... the custom maximize button does not behave the same way as the native maximize button. The custom button will expand the playlist window either to the left/right of the main app window, or above or below it (depending on the relative locations of both windows), so that both are always visible and do not overlap. This is the intended maximize behavior. On the other hand, the native control does not know or care about the main app window and will maximize it over the entire screen. This is not really the intended behavior.
Also, I don't know if the OS controls will do something weird with the window, i.e. unexpected behavior, in some cases that we have not foreseen. By allowing control only through custom controls, you take the uncertainty out of the equation, and everything behaves in very predictable and consistent ways as you have programmed it. That's why I didn't like the playlist window having native controls.
But again, experiment away, and if you find that the new layout of controls is intuitive and behaves predictably, then, that's fine too. I'm open to new ways of doing things ... absolutely.
At the moment, I'm working on playlist drag/drop re-ordering. It is quite involved, as it turns out ! Another thing I'm looking to add is new playlist views that will group the tracks by album/artist/genre, etc, like iTunes does. I may or may not do it. The playlist could have a tab group, one for each grouped view, plus the default flat view.
BTW, my GitHub page has a "Planned Updates" section on the main page. If you're ever wondering what I'm up to, that's a great place to checkJust FYI.
The custom minimize button calls miniaturize() which is supposed to hide/minimize the window. Weird. Maybe Swift 4 has a new way of doing that.
[doublepost=1508220218][/doublepost]
------------------------------------------------------------------------------------------
Since my developer-readme file is totally outdated at this point, and since you're actively working on the code, I just wanted to give you a few pointers into my code (although I'm aware that you like to figure things out on your own). I will also update developer-readme soon.
App layers: The app definitely implements the MVC design pattern, but I like to call them "View", "Delegate", and "Back end"
The view layer consists of several ViewController classes, one for each logical component of the app (not necessarily physical component, but logical component). For instance, all playback-related functions are handled by PlaybackViewController. And, there are several custom views, e.g. XXXSliderCell, that customize the look and feel of UI controls.
PlaylistTableViewController: This class is so important it deserves a special mention. This class provides the data for the playlist table view. It also handles drag/drop operations.
The delegate layer takes requests from the view layer, and marshals them into formats the back end can use. It does things like conversion of values from a UI format to the audio engine's format, and formatting of back end values into UI-friendly formats. E.g. volume in the UI ranges from 0-100, but the engine requires 0-1. Generally, the delegate layer provides the view layer with a simple interface for accessing the audio engine in the back end, and provides app-level operations that then get translated into audio-engine-level operations. The delegate layer consists of several protocols, that are named XXXDelegateProtocol, which provide the contracts, and of course their concrete implementations. One rule that is always followed is that the view layer never accesses the back end directly; it must always only interact with the delegate layer.
Finally, the back end, also accessed through protocols, consists of 3 main components:
1 - Audio Graph: centers around the AVAudioEngine framework. The "audio graph" refers to a graph of nodes that perform the necessary signal processing (playback, effects, mixing, etc) that sends the sound output to your audio device. The main classes here are the AudioGraph, Player, and Recorder. BufferManager does all the dirty work of scheduling audio buffers for Player.
2 - Playlist: CRUD for tracks. Also performs search/sort functions.
3 - PlaybackSequence: Contains logic for repeat and shuffle. Decides which track is going to play next.
As much as possible, code is accessed through protocols, not directly through concrete implementation classes, which is a fundamental OOP principle.
Example call chain: As an example, when the user changes the volume using the volume slider, control flows as follows: AudioGraphViewController -> AudioGraphDelegate (through AudioGraphDelegateProtocol) -> AudioGraph (through AudioGraphProtocol) -> AVAudioPlayerNode.setVolume().
View -> Delegate -> Back end
Another example: When a track is removed from the playlist, the following happens:
PlaylistViewController -> PlaylistDelegate -> PlaylistMutatorDelegate -> Playlist.removeTracks() (through PlaylistCRUDProtocol)
Object Graph: Constructs/initializes all the delegate/back end objects necessary.
AppState: Class that encapsulates all persistent app state. When you exit the app, this object is persisted to a json file, and loaded back upon startup.
Preferences: User preferences. Uses the built-in system-provided UserDefaults mechanism.
Messaging: A very important part of the app is messaging, both synchronous and asynchronous. Often, one app component does its job and needs to tell other app components that it has done something, so that the other component may do something else in response. But, it has to do this without tight coupling between the two components. For instance, when a playlist track is removed from the playlist, if the removed track was playing, the playlist view needs to tell the playback controller to stop playing the track, so it will send out a message.
Another example - When the app is about to exit, the app delegate will send out an "exiting" message. If a recording is ongoing, the recorder unit will respond with "Wait ! Don't exit, let me prompt the user to save his recording".
The two main files that hold message definitions are SyncMessages.swift and AsyncMessages.swift.
IO and Utils: The IO and Utils source groups (folders within the project) contain a set of utility classes for performing disk IO and other functions like formatting strings, performing UI computations, concurrency, file system computations, etc.
MetadataSpecs: ID3 and ITunes metadata specifications which map format-specific tags like TLEN to user-friendly descriptions like "Duration"
Track lazy loading: One important thing to note is that track info is lazily loaded for optimal performance. When you add files to the playlist, no info whatsoever is read from the track. Then, other info (duration, artist, title, art) are read asynchronously, and the playlist updated. Finally, when you actually play the track, or prepare the track for playback (which is done eagerly), it's audio track is actually read. TrackIO is the class that handles the loading of track info.
Hope this helps !
It makes my eyes bleed so I won't use it. But kudos to you! So many times I've been in a situation where I can't find an app for my needs, but unfortunately I never learned how to code.
Only problem I have is, when first opening the application the playlist window overlaps slightly the main window. When pressing the 'dock to bottm' button it somehow works just fine. I wonder what's the difference here on launch?
And this is how it looks right now:
View attachment 725972
// Window width (never changes)
static let windowWidth: CGFloat = 415
static let minPlaylistWidth: CGFloat = 415
static let minPlaylistHeight: CGFloat = 150
// Window heights for different views
static let windowHeight_compact: CGFloat = 208
static let windowHeight_playlistAndEffects: CGFloat = 381
static let windowHeight_playlistOnly: CGFloat = 196
static let windowHeight_effectsOnly: CGFloat = 393
Regarding the indicator for the playlist I will go for the IINA route and just insert a text symbol like this: ▶︎
Looks good and is easy on the system resources.
Thanks for pointing me to those UIConstants, totally forgot these. But are these really neccessary? I mean the 'dock playlist' functions seem to work with the actual current window size. So when the main window already exists it should dock at the right position by calculating the coordinates.
I would only consider making them configurable when there's a better way to implement them, without increasing CPU load THAT much. Static placeholders should be fine for now IMO and animated ones should have low priority. I found them even a bit annoying after a while. But that's just my opinion
EDIT: Forgot to mention, but I'm also in the process of inserting comment marks throughout the whole code, so it's a LOT easier to navigate.
The static text for the playing track display is great for now. I was surprised at how hard the GIF is on the system ! Wow ! Never suspected that it would be so bad.
The UIConstants are not strictly necessary, but they simplify the code a LOT ! I remember this from when I implemented that piece of code. The code becomes much more concise and readable when it doesn't have to calculate window height everytime something is toggled. Just pick up the constant value and set the height.
Besides, those window heights will never change. For a given view, the main window height will always be X. Only the playlist is resizable by the user. The main window has a fixed set of possible heights (depending on the views shown).
I guess it's a tradeoff - code simplicity vs duplication. I err on the side of simplicity in this case.
Ok, fixed the docking issue. Now it's totally independent to what size the main window has. It just docks like it should even on application launch.
All three dockPlaylist functions got merged into this single one, which takes the type (bottom, left, right) as parameter.
Saves a lot of space this way.
View attachment 726012
I will also add a gap constant, because i like it better when there's a small gap between the docked windows.
Bist du Deutscher ?
This is a good time to tell you that I will possibly be moving onto another project soon ... a writing project. So, I'm trying to wrap up my planned changes soon, so that I can move on.
If I do move on, you will likely be the chief developer for this project, and I will take a backseatI will come back to it occasionally, more than likely, but I am anticipating being on that other project almost full-time.
Ja bin ich, du etwa auch?
Oh, sad to hear you're leaving this project. Still looking forward to those changes.
Thanks for entrusting me the development of this app. Yet, I'm not sure if i can handle a project of this size alone. Will take some time for sure until I can get a stable release out there. But i will keep doing test builds until im satisfied to merge them with the Swift 4 branch.
I wish you all the best for your new project, and will be looking forward to hearing from you every now and then.