Merge lp:~ywwg/mixxx/features_xwax2 into lp:~mixxxdevelopers/mixxx/trunk

Proposed by Owen Williams
Status: Merged
Merged at revision: 2753
Proposed branch: lp:~ywwg/mixxx/features_xwax2
Merge into: lp:~mixxxdevelopers/mixxx/trunk
Diff against target: 7961 lines (+3993/-1888)
59 files modified
mixxx/build/depends.py (+2/-0)
mixxx/build/features.py (+4/-1)
mixxx/build/qtcreator/mixxx.pro (+1/-0)
mixxx/lib/xwax/lut.c (+110/-0)
mixxx/lib/xwax/lut.cpp (+110/-0)
mixxx/lib/xwax/lut.h (+42/-0)
mixxx/lib/xwax/pitch.h (+71/-0)
mixxx/lib/xwax/timecoder.c (+381/-449)
mixxx/lib/xwax/timecoder.h (+80/-42)
mixxx/lib/xwax/timecoder_win32.cpp (+346/-411)
mixxx/res/skins/Outline1024x600-Netbook/skin.xml (+77/-0)
mixxx/src/cachingreader.cpp (+25/-3)
mixxx/src/controlobject.cpp (+5/-2)
mixxx/src/controlpushbutton.cpp (+25/-44)
mixxx/src/controlpushbutton.h (+2/-0)
mixxx/src/controlvaluedelegate.cpp (+3/-0)
mixxx/src/dlgpreferences.cpp (+19/-1)
mixxx/src/dlgpreferences.h (+12/-10)
mixxx/src/dlgprefnovinyl.cpp (+32/-0)
mixxx/src/dlgprefnovinyl.h (+39/-0)
mixxx/src/dlgprefnovinyldlg.ui (+475/-0)
mixxx/src/dlgprefsound.cpp (+5/-2)
mixxx/src/dlgprefvinyl.cpp (+77/-120)
mixxx/src/dlgprefvinyl.h (+3/-9)
mixxx/src/dlgprefvinyldlg.ui (+130/-28)
mixxx/src/engine/bpmcontrol.cpp (+1/-1)
mixxx/src/engine/enginebuffer.cpp (+177/-46)
mixxx/src/engine/enginebuffer.h (+24/-5)
mixxx/src/engine/enginebufferscalelinear.cpp (+224/-125)
mixxx/src/engine/enginebufferscalelinear.h (+13/-3)
mixxx/src/engine/enginedeck.cpp (+2/-1)
mixxx/src/engine/ratecontrol.cpp (+20/-1)
mixxx/src/engine/ratecontrol.h (+14/-8)
mixxx/src/engine/readaheadmanager.cpp (+12/-10)
mixxx/src/mathstuff.cpp (+10/-0)
mixxx/src/mathstuff.h (+9/-0)
mixxx/src/midi/midiscriptengine.cpp (+3/-3)
mixxx/src/mixxx.cpp (+143/-38)
mixxx/src/mixxx.h (+7/-1)
mixxx/src/sounddeviceportaudio.cpp (+2/-2)
mixxx/src/soundmanager.cpp (+56/-27)
mixxx/src/soundmanager.h (+6/-1)
mixxx/src/soundmanagerutil.cpp (+15/-0)
mixxx/src/soundmanagerutil.h (+1/-0)
mixxx/src/vinylcontrol.cpp (+41/-24)
mixxx/src/vinylcontrol.h (+68/-46)
mixxx/src/vinylcontrolproxy.cpp (+28/-2)
mixxx/src/vinylcontrolproxy.h (+14/-11)
mixxx/src/vinylcontrolsignalwidget.cpp (+115/-150)
mixxx/src/vinylcontrolsignalwidget.h (+21/-16)
mixxx/src/vinylcontrolxwax.cpp (+701/-159)
mixxx/src/vinylcontrolxwax.h (+72/-31)
mixxx/src/waveform/waveformrendersignal.cpp (+1/-3)
mixxx/src/widget/wnumberpos.cpp (+10/-4)
mixxx/src/widget/wnumberrate.cpp (+0/-1)
mixxx/src/widget/wnumberrate.h (+1/-0)
mixxx/src/widget/woverview.cpp (+4/-3)
mixxx/src/widget/wstatuslight.cpp (+95/-37)
mixxx/src/widget/wstatuslight.h (+7/-7)
To merge this branch: bzr merge lp:~ywwg/mixxx/features_xwax2
Reviewer Review Type Date Requested Status
William Good Approve
RJ Skerry-Ryan Approve
Albert Santoni Needs Fixing
Review via email: mp+37798@code.launchpad.net

Description of the change

This branch revamps vinyl control. It:

* updates xwax to the latest version
* makes changes to the engine code to support prerolling of tracks (positions < 0)
* tweaks some of the UI widgets (multistate buttons, multistate lights)
* rewrites the vinyl control code
* rewrites the linear scalar to work better

To post a comment you must log in.
Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

This doesn't merge cleanly with the latest trunk. Can you please merge with trunk? The entire SConscript was refactored. It looks like from your diff, you only added two lines to the SConscript. I've produced a diff of what you need to change in the new SConscript based on that. When you merge with trunk, only SConscript.env will be conflicted. Replace whatever is in your copy with the trunk version, and apply this patch to mixxx/build/features.py:

=== modified file 'mixxx/build/features.py'
--- mixxx/build/features.py 2010-10-06 18:34:43 +0000
+++ mixxx/build/features.py 2010-10-06 23:20:56 +0000
@@ -198,6 +198,8 @@
             sources.append("#lib/xwax/timecoder_win32.c")
         else:
             sources.append("#lib/xwax/timecoder.c")
+ sources.append("#lib/xwax/lut.c")
+ sources.append("#lib/xwax/pitch.c")
         return sources

 class Tonal(Feature):

Revision history for this message
RAFFI TEA (raffitea) wrote :

Does it also compile on Windows?

Revision history for this message
Owen Williams (ywwg) wrote :

> Does it also compile on Windows?
I think the new version of xwax needs to be checked. In the existing trunk, there's a _win32 file that was created, but I don't know how. It might need to be recreated for the new version. You could always try compiling it and see what happens :)

Revision history for this message
Albert Santoni (gamegod) wrote :

There's a bunch of C99 stuff in the original timecoder.c that I fixed
to compile with MSVC on Windows (no C99). Mainly, I remember it was
declaration of the timecoder struct. It should be mergeable by eye,
and I'll do it when I get around to building the branch on Windows if
someone else doesn't beat me to it.

On Wed, Oct 13, 2010 at 5:33 AM, Owen Williams <email address hidden> wrote:
>> Does it also compile on Windows?
> I think the new version of xwax needs to be checked.  In the existing trunk, there's a _win32 file that was created, but I don't know how.  It might need to be recreated for the new version.  You could always try compiling it and see what happens :)
> --
> https://code.launchpad.net/~ywwg/mixxx/features_xwax2/+merge/37798
> Your team Mixxx Development Team is requested to review the proposed merge of lp:~ywwg/mixxx/features_xwax2 into lp:mixxx.
>

Revision history for this message
Phillip Whelan (pwhelan) wrote :

There are also some other issues that I know your code has or had:

  * Enabling Vinyl Control on one deck sometimes locks up the other
  * Old Skins or skins that are not updated to account for your widget
changes crash mixxx.

I'll check out your latest version of the branch to see if these are fixed.

lp:~ywwg/mixxx/features_xwax2 updated
2474. By Owen Williams <owen@ywwg>

* merge from trunk and fix conflicts

2475. By Owen Williams <owen@ywwg>

* we don't need to worry about device problems here (I think?)

2476. By Owen Williams <owen@ywwg>

* merge from trunk -- ugh, tough merge

2477. By Owen Williams <owen@ywwg>

* fix startup crash. Don't try to initialize cotnrolobject values right after creating the threads

2478. By Owen Williams <owen@ywwg>

* fix pushbuttons to work with multistate pushbutton widgets
* fix pushbuttons so that toggles work with midi controllers that do toggling (nanokontrol)
* fix vinyl code so that it's not specific to my nanokontrol setup
* simplify mode-switching code: don't allow switch to ABS mode if playing and needleskip prevention is on

2479. By Owen Williams <owen@ywwg>

* merge with trunk, fix conflicts

2480. By Owen Williams <owen@ywwg>

* merge from lp:mixxx

2481. By Owen Williams <owen@ywwg>

* fix vinyl control checkboxes not working

2482. By Owen Williams <email address hidden>

* merge from lp:mixxx

2483. By Owen Williams <email address hidden>

* merge from lp:mixxx
* adapted to new pref dialog design
* adapted vinyl code to work with new n-deck design.

2484. By Owen Williams <email address hidden>

* remove my personal library unscanner hack

2485. By Owen Williams <email address hidden>

* merge with crazy fast-moving lp:mixxx

Revision history for this message
Albert Santoni (gamegod) wrote :

Notes from an email conversation between Owen and I:

- Keyboard controls are a bit busted due to modifications made to controlpushbutton.cpp. Should probably revert these and find a better solution to Owen's MIDI problems another way.

- Must change the vinyl control code to be able to deal with a single deck set on any of the inputs, or figure out just how this should work in general. The solution must be generalizable to N-decks of vinyl control input, so we probably don't want to hardcode "Deck 1" as a magic deck.
- Should also figure out oddball cases like what happens if you have two decks of input and 3 players in Mixxx. How does the routing work?

review: Needs Fixing
lp:~ywwg/mixxx/features_xwax2 updated
2486. By Owen Williams <email address hidden>

* redo vinyl input mapping to support arbitrary numbers of decks
* to enable "single deck mode" set both decks to the same set of inputs

2487. By Owen Williams <email address hidden>

* fix constant mode playback speed

2488. By Owen Williams <email address hidden>

* fix incorrect comments
* fix enable/disable of vinyl mode to be smooth and glitch-free

2489. By Owen Williams <email address hidden>

* fix wstatuslights

2490. By Owen Williams <email address hidden>

* merge from lp:mixxx

Revision history for this message
Albert Santoni (gamegod) wrote :

- The guy who made the Trancer skin originally had another skin which

was completely ripped off from Traktor. In addition, the Trancer skin

has been removed from KDE-Look, so I'd prefer to err on the side of

caution and not include the Trancer skin. It's probably ripped off

from somewhere, I just don't know where yet.

- I had previously patched timecoder.c to make it store the lookup

table in the timecoder struct. This is absolutely necessary otherwise

you end up with a race condition because the code isn't thread safe and you will get crashes and all sorts of weird problems.

(eg. compare the new timecoder_free_lookup() with mine)

- Can the "enable vinyl control" menu items be moved into a submenu off the Options menu like :

Options
  Vinyl Control -> Enable Deck 1
                   Enable Deck 2

- Launchpad is wrong, the diff is incomplete (why??) so this is not a full review yet.

- VinylModeSwitch.js - What does this do?

- I can't get taglib built on Windows with MSVC so I haven't had a chance to try to get the new xwax code building on Windows.

review: Needs Fixing
lp:~ywwg/mixxx/features_xwax2 updated
2491. By Owen Williams <email address hidden>

* remove unneeded javascript file

Revision history for this message
Owen Williams (ywwg) wrote :

> - Launchpad is wrong, the diff is incomplete (why??) so this is not a full review yet.

what do you mean the diff is incomplete?

>
> - VinylModeSwitch.js - What does this do?

that was a workaround I was using for a while. It's deprecated so I'll
remove it.

> - I had previously patched timecoder.c to make it store the lookup
>
> table in the timecoder struct. This is absolutely necessary otherwise
>
> you end up with a race condition because the code isn't thread safe and you will get crashes and all sorts of weird problems.
>
> (eg. compare the new timecoder_free_lookup() with mine)
>

I don't think this needs to be done again. Looking in the newer xwax, it already is storing the def in the timecoder, and the def stores the lookup table in itself. It looks to me like it's doing what you describe. (That and it doesn't crash or give me weird problems)

lp:~ywwg/mixxx/features_xwax2 updated
2492. By Owen Williams <email address hidden>

* move vinyl control options to submenu

2493. By Owen Williams <email address hidden>

* when turning off vinyl control, set play button based on current speed

2494. By Owen Williams <email address hidden>

* removed infringing skin
* modified outlinenetbook with basic vinyl indicators and buttons

Revision history for this message
Albert Santoni (gamegod) wrote :

> > - Launchpad is wrong, the diff is incomplete (why??) so this is not a full
> review yet.
>
> what do you mean the diff is incomplete?
>

The diff that Launchpad generated doesn't seem to have all the changes that you made in your branch, for whatever reason (eg. it was missing the vinylcontrolxwax.cpp changes). Anyways, looks like it's been fixed now.

> >
> > - VinylModeSwitch.js - What does this do?
>
> that was a workaround I was using for a while. It's deprecated so I'll
> remove it.

Ok, thanks!

>
>
> > - I had previously patched timecoder.c to make it store the lookup
> >
> > table in the timecoder struct. This is absolutely necessary otherwise
> >
> > you end up with a race condition because the code isn't thread safe and you
> will get crashes and all sorts of weird problems.
> >
> > (eg. compare the new timecoder_free_lookup() with mine)
> >
>
> I don't think this needs to be done again. Looking in the newer xwax, it
> already is storing the def in the timecoder, and the def stores the lookup
> table in itself. It looks to me like it's doing what you describe. (That and
> it doesn't crash or give me weird problems)

The problem is that the timecode_def array is a global variable in timecoder.c. When we build the lookup table in both our VinylControlXwax threads, they can be concurrently writing to the same memory, in that timecode_def array. As well, when we free the lookup table, we're double-freeing the same memory. It's a subtle problem but it will cause crashes. You probably won't see it most of the time because it's a race condition.

The code has diverged mainly with the addition of this lut struct to timecode_def_t. Not sure if it's better to go back and merge our old timecoder.c with the latest one or whether I should try to redo all the changes from scratch to make it re-entrant again.

Revision history for this message
Albert Santoni (gamegod) wrote :

After staring at the xwax code for a while, I think it's going to be quite a bit of work to get the changes right, and in the end it would probably result in another forked piece of code (hard to maintain).

I think what I'm going to do is modify vinylcontrolxwax.cpp to wrap the stock timecoder.c in a way make it thread-safe. We will lose the ability to have different timecode types on different decks because of this, but it's a restriction of xwax right now. (My modifications from before overcame this, but it's probably not worth maintaining.)

I'll see how this goes...

Revision history for this message
Albert Santoni (gamegod) wrote :

void EngineBufferScaleLinear::setBaseRate(double dBaseRate)

- Why did you add the isNan checks here? Were you seeing NaNs appearing in this code? (If so, it might have been a symptom of a bigger problem...)

lp:~ywwg/mixxx/features_xwax2 updated
2495. By Owen Williams <email address hidden>

* remove old isNan workaround

2496. By Owen Williams <email address hidden>

* remove library changes that shouldn't be in this branch

2497. By Owen Williams <email address hidden>

* merge from lp:mixxx

Revision history for this message
Sean M. Pappalardo (pegasus-renegadetech) wrote :

xwax v0.8 is to be released very soon: http://www.xwax.co.uk/releases/xwax-0.8.tar.gz

Is it worth updating to that version before merging this?

Revision history for this message
Owen Williams (ywwg) wrote :

Yeah it's worth trying to merge that in. It looks like the API hasn't changed a lot so it shouldn't be that hard.

Revision history for this message
Albert Santoni (gamegod) wrote :

In my features_xwax2_albert branch, tonight I fixed:
- keyboard and MIDI buttons on my controllers (basically reverted the behaviour in ControlPushButton)
- a crash in VinylControlXwax due to the code stepping out of bounds in the dPitch ringbuffer:

                 if(ringPos++ > RING_SIZE)

ringPos++ returns "ringPos" then increments the value. ++ringPos would have done what you wanted, but I've just the ringPos++ before the if statement so it's less confusing for the next person who looks at this code.

I also fixed a bug in trunk where channel pairs with non-even base numbers could be selected (like channels 2-3 instead of 3-4), which was preventing me from using my second input pair.

What else do we need to fix?

lp:~ywwg/mixxx/features_xwax2 updated
2498. By Owen Williams <email address hidden>

* merge from lp:~mixxxdevelopers/mixxx/features_xwax2_albert:
* Fixed out-of-bounds array access in VinylControlXwax ringbuffer.
* Fixed MIDI buttons and keyboard buttons for most controllers.
* Renamed xwax files to .cpp to make Visual Studio compile them as C++. (This is easier than re-implementing missing C99 stuf$
* Instead of re-implementing re-entrancy modifications in xwax's timecoder, I wrapped the relevent code in a static mutex, wh$
* New xwax vinyl control code compiles on Windows now

* also update xwax to 0.8
* fix compilation under linux (as opposed to windows)

2499. By Owen Williams <email address hidden>

* restore my the "safe" values I found work better

2500. By Owen Williams <email address hidden>

* don't resync if near start of track (prevents annoying jumping)

2501. By Owen Williams <email address hidden>

* aw hell, typo

2502. By Owen Williams <email address hidden>

* merge from lp:mixxx

Revision history for this message
William Good (bkgood) wrote :

Owen- it looks like you got this in your latest merge, but your SoundManager code may need updated. I changed SoundDevice to not error when it gets an AudioInput whose channels overlap with another Input. This way, a user can send audio input from DevX Channels 1-2 to vinyl control 1 and 2 and then the VC code can decide which deck to apply that vinyl control signal to (I'm guessing that's what you're doing?). This should make your life in SoundManager a little easier.

Revision history for this message
Owen Williams (ywwg) wrote :

Actually... I was kind of relying on that error :). I have to build a map of the vinyl inputs, and if I get no error, then I know it's a new set of inputs. If I get an error, then I know it's a duplicate set of inputs and I have to find the previous inputs it corresponds to.

I think I can rewrite the test pretty easily, though, and this will be not as hacky.

lp:~ywwg/mixxx/features_xwax2 updated
2503. By Owen Williams <email address hidden>

* duplicate_inputs is no longer used at all, so remove it completely

Revision history for this message
RAFFI TEA (raffitea) wrote :

Owen, I'd like to test on Windows but I can't check out your branch with 'bzr branch ...'
There's a file called 'lut.cpp' in lib/xwax which is a symbolic link. BZR on Windows does not support symbolic links. Please resolve that problem!

Thanks,

Tobias

Revision history for this message
Owen Williams (ywwg) wrote :

ok fixed. It's just a duplicate of lut.c. I thought perhaps bzr would be smart enough to compensate for that on windows

lp:~ywwg/mixxx/features_xwax2 updated
2504. By Owen Williams <email address hidden>

* remove symbolic link for compatibility on windows

2505. By Owen Williams <email address hidden>

* fix bug causing static in linear scaler

2506. By Owen Williams <email address hidden>

* merge from lp:mixxx

Revision history for this message
JWC (jwc) wrote :

With QT 4.7.1 (current version in Arch Linux repositories), I experience segfaults whenever I try to change something very quickly. Examples include moving a knob quickly (both through a MIDI controller and with a mouse), scratching (i.e. moving the waveform quickly), and anything else that modifies the GUI rapidly.

Here's a backtrace:
http://f.jwcxz.com/mixxx.bt

It looks like the problem is with QPixmap::isNull().

The second issue that I found was that after the segfault occurs, vinyl control is broken when I start up Mixxx again. To fix it, I need to go into the audio i/o preferences, change some value and change it back again (to enable the "Apply" button) and then click "Apply." This will allow the vinyl controller to work again (turning vinyl control off and back on again without doing this doesn't have any effect).

The third issue was that the gain bars in the vinyl control setup page don't seem to be showing any activity (they just show the grid background).

Lastly, as I understand it, scratch mode has been disabled. So, the option for it should be removed from the vinyl control options in the preferences.

Revision history for this message
JWC (jwc) wrote :

I completely forgot another bug that I experienced. When I have
"Pitch/Rate slider direction" set to "Down increases speed" in the
Interface tab of the preferences, the vinyl control doesn't respect
that. That is, when I slow the platter down, the waveform stretches the
wrong way and the BPM on the track indicator increases.

> With QT 4.7.1 (current version in Arch Linux repositories), I experience
> segfaults whenever I try to change something very quickly. Examples include
> moving a knob quickly (both through a MIDI controller and with a mouse),
> scratching (i.e. moving the waveform quickly), and anything else that modifies
> the GUI rapidly.
>
> Here's a backtrace:
> http://f.jwcxz.com/mixxx.bt
>
> It looks like the problem is with QPixmap::isNull().
>
> The second issue that I found was that after the segfault occurs, vinyl
> control is broken when I start up Mixxx again. To fix it, I need to go into
> the audio i/o preferences, change some value and change it back again (to
> enable the "Apply" button) and then click "Apply." This will allow the vinyl
> controller to work again (turning vinyl control off and back on again without
> doing this doesn't have any effect).
>
> The third issue was that the gain bars in the vinyl control setup page don't
> seem to be showing any activity (they just show the grid background).
>
> Lastly, as I understand it, scratch mode has been disabled. So, the option
> for it should be removed from the vinyl control options in the preferences.

lp:~ywwg/mixxx/features_xwax2 updated
2507. By Owen Williams <email address hidden>

* fix vinyl input not working on startup (needed to set gain value)

2508. By Owen Williams <email address hidden>

* remove unused "scratch" mode

2509. By Owen Williams <email address hidden>

* respect user's choice of pitch slider direction

Revision history for this message
Albert Santoni (gamegod) wrote :

Hey Owen,

Thanks for fixing stuff so quickly. I just ran valgrind on your branch, and this was the result after ~30 seconds:
http://pastebin.com/MzE56Wkj

I ran valgrind for several hours on trunk earlier today and didn't see the QWidget painting warnings nor the EBSL errors.

I did see the inflateReset2 warnings in trunk though.

I was using the outlineNetbook skin during these tests. This might suggest there's a problem in WStatusLight... ? (as well as EBSL in your branch, trunk seemed fine)

Thanks!
Albert

lp:~ywwg/mixxx/features_xwax2 updated
2510. By Owen Williams <email address hidden>

* initialization bug

Revision history for this message
Owen Williams (ywwg) wrote :

I just pushed a possible fix for this bug. There was a for-loop:

for (int i=0; i<m_iNoPos; ++i)

Which I think means that it starts at 1, not 0? I don't really know how to read valgrind so I'm not sure if that was the problem.

Revision history for this message
JWC (jwc) wrote :

I was still getting segfaults with revision 2510, but I think I see the problem.

When moving knobs really fast (in any skin), you eventually arrive to this backtrace:
http://f.jwcxz.com/mixxx.bt
(Updated because I was an idiot and forgot to turn off symbol stripping)

After doing some poking in wstatuslight.cpp, I found that at one point, m_iPos was 127, but m_iNoPos was only 1. Therefore, we are trying to access a bad place of memory in line 117. I'm not sure what's causing m_iPos to become 127 -- I don't know enough about the code.

However, when I changed line 113 from:
    if (m_pPixmapSLs[m_iPos])
to
    if (m_iPos < m_iNoPos && m_pPixmapSLs[m_iPos])

I couldn't reproduce the segfaults any more.

This probably isn't the "correct" fix (although I couldn't see any visible result of the change other than it not crashing), because something is likely causing m_iPos to be set incorrectly.

lp:~ywwg/mixxx/features_xwax2 updated
2511. By Owen Williams <email address hidden>

* workaround improper position values getting set

2512. By Owen Williams <email address hidden>

* oops, test the right variable

Revision history for this message
Owen Williams (ywwg) wrote :

You rock, nice debugging. I tweaked the code so that the improper values are just thrown out completely so m_iPos doesn't get set to an invalid value.

I'll see if I can figure out why that wrong value is being set, but it might be something simple like some lights consider "127" to be on instead of "1". Perhaps that's a special case.

Revision history for this message
JWC (jwc) wrote :

No problem, thanks for the nice new features! I'll also continue testing and debugging whenever I get a chance. Otherwise, your branch seems to be working fine now, except for the volume gain on the vinyl control preferences panel. That might also be my window manager causing problems, so you'll probably need someone else to test that.

> You rock, nice debugging. I tweaked the code so that the improper values are
> just thrown out completely so m_iPos doesn't get set to an invalid value.
>
> I'll see if I can figure out why that wrong value is being set, but it might
> be something simple like some lights consider "127" to be on instead of "1".
> Perhaps that's a special case.

Revision history for this message
Owen Williams (ywwg) wrote :

Ah, I got it. The PeakIndicator used in the LateNight theme (which was crashing) is a statuslight, and that sets the value to 1, or in midi, 127. The old code set the light to off when the value was 0 and on when it was anything else.

So I changed the code so that for two-state lights, the old way applies: 0=off, nonzero=on. For multistate status lights (like mine), the values have to be exact.

This should solve the crashes (I hope).

lp:~ywwg/mixxx/features_xwax2 updated
2513. By Owen Williams <email address hidden>

* fix (finally?) status light crashes and non-lighting boolean lights

Revision history for this message
Albert Santoni (gamegod) wrote :

Thanks Owen! I will try to run Valgrind again on your branch to confirm as
soon as I have a chance.
On 2010-11-17 1:56 PM, "Owen Williams" <email address hidden> wrote:
> Ah, I got it. The PeakIndicator used in the LateNight theme (which was
crashing) is a statuslight, and that sets the value to 1, or in midi, 127.
The old code set the light to off when the value was 0 and on when it was
anything else.
>
> So I changed the code so that for two-state lights, the old way applies:
0=off, nonzero=on. For multistate status lights (like mine), the values have
to be exact.
>
> This should solve the crashes (I hope).
> --
> https://code.launchpad.net/~ywwg/mixxx/features_xwax2/+merge/37798
> You are reviewing the proposed merge of lp:~ywwg/mixxx/features_xwax2 into
lp:mixxx.

Revision history for this message
JWC (jwc) wrote :

Oops, it looks like features_xwax2, when merged with the last revision of trunk that didn't have conflicts (i.e. probably not a problem), doesn't control the second deck with the second vinyl input!

I just got my second audio card in today so that I could finally allocate one turntable to the first deck and the other to the second deck. However, both decks follow one turntable, no matter what I set the second deck's input to.

It sounds like just a simple typo or something. I'll try to look through the code to figure out what it is, but Owen probably has a better idea.

lp:~ywwg/mixxx/features_xwax2 updated
2514. By Owen Williams <email address hidden>

* merge with lp:mixxx -r 2552

2515. By Owen Williams <email address hidden>

* merge with lp:mixxx -r 2557

2516. By Owen Williams <email address hidden>

* merge with lp:mixxx -r 2559

2517. By Owen Williams <email address hidden>

* don't print to cout

2518. By Owen Williams <email address hidden>

* use the control object value for samplerate instead of config value. this solves wrong samplerate bugs (because
  the config file can be out of date when using JACK)

2519. By Owen Williams <email address hidden>

* make sure vinyl input hashes are truly unique

Revision history for this message
JWC (jwc) wrote :

Sorry, I have another bug to report. Looks like waveform/BPM scaling features have disappeared? Possibly a conflict resolution problem?

lp:~ywwg/mixxx/features_xwax2 updated
2520. By Owen Williams <email address hidden>

* confirm device samplerate matches configured samplerate (can be wrong with JACK)
  and reopen devices and vinyl controllers if there's a discrepency

2521. By Owen Williams <email address hidden>

* fix not respecting slider direction when switching to constant mode

2522. By Owen Williams <email address hidden>

* sigh, yet another stab at getting samplerates to all agree

2523. By Owen Williams <email address hidden>

* only analyze samples for the input device we were asked to analyze

2524. By Owen Williams <email address hidden>

* step 1 of fixing linear scaler

2525. By Owen Williams <email address hidden>

* step two of rewriting the scaler. sounds better maybe?

2526. By Owen Williams <email address hidden>

* audio work step three, pre-cleanup

2527. By Owen Williams <email address hidden>

* finally done(?) fixing the scaler

2528. By Owen Williams <email address hidden>

* more soul-crushing work on the scaler
* still bad clicks and pops when rapidly changing speed

2529. By Owen Williams <email address hidden>

* another scaler tweak

2530. By Owen Williams <email address hidden>

* I'll live with it. scaler work

2531. By Owen Williams <email address hidden>

* clean up (scaler)

2532. By Owen Williams <email address hidden>

* tweak ramps

2533. By Owen Williams <email address hidden>

* fixed it. really. the scaler works.

2534. By Owen Williams <email address hidden>

* fix occasional incorrect bufferings, and leave in all my debug crap (commented out)
* attempted to create multi-buffer ramp-in, failed miserably, have no idea why

2535. By Owen Williams <email address hidden>

* merge with lp:mixxx 2580

2536. By Owen Williams <email address hidden>

* undo my JACK samplerate fixes to prepare for alternative trunk fix

2537. By Owen Williams <email address hidden>

* merge lp:mixxx 2581

2538. By Owen Williams <email address hidden>

* fix end-of-track playbutton flashing

2539. By Owen Williams <email address hidden>

* fix end-of-track behavior

2540. By Owen Williams <email address hidden>

* try using two samples for rampout
* tried holding last sample, not setting to zero -- meh

2541. By Owen Williams <email address hidden>

* check for end-of-track after we resync in case we're no longer
  at end of track

2542. By Owen Williams <email address hidden>

* more tweaks to scaler because I just can't stop myself

2543. By Owen Williams <email address hidden>

* based on profiling data, flip loop in pushBuffer -- massive CPU use reduction!

2544. By Owen Williams <email address hidden>

* hrm, I thought that wouldn't happen, but no worries, it'll just loop again

Revision history for this message
JWC (jwc) wrote :

Aww, merge conflicts. :(

lp:~ywwg/mixxx/features_xwax2 updated
2545. By Owen Williams <email address hidden>

* merge from lp:mixxx

2546. By Owen Williams <email address hidden>

* merge from lp:mixxx

Revision history for this message
Owen Williams (ywwg) wrote :

yeah yeah... trunk moves fast. Should be ok now.

lp:~ywwg/mixxx/features_xwax2 updated
2547. By Owen Williams <email address hidden>

* merge from lp:mixxx

2548. By Owen Williams <email address hidden>

* make doubly sure that vinylcontrol is off if there's no vinyl input

2549. By Owen Williams <email address hidden>

* split the scale function into two, avoiding an awful
  hack involving dangerously reassigning the buffer member.

2550. By Owen Williams <email address hidden>

* fix bug in last revision (was double-updating a member var)

2551. By Owen Williams <email address hidden>

* try to prevent spurious needleskip detections

2552. By Owen Williams <email address hidden>

* that eliminated needleskip detection completely, revert

Revision history for this message
JWC (jwc) wrote :

I can't say that this is xwax2's (revision 2546) fault yet and this is going to be a really hard one to debug, but twice now, I've noticed after a few hours of mix[xx]ing, the second deck suddenly loses its vinyl control and just plays on its own. I have to go into the sound preferences and re-apply the audio devices in order to refresh things and make the vinyl control work again. I have no clue how to reproduce this problem and it's going to be difficult to find the root cause of it, but I'll spend January debugging as much as possible.

Has anyone else noticed this? Has anyone else been debugging this branch?

lp:~ywwg/mixxx/features_xwax2 updated
2553. By Owen Williams <email address hidden>

* merge from lp:mixxx

2554. By Owen Williams <email address hidden>

* merge from lp:mixxx

Revision history for this message
Albert Santoni (gamegod) wrote :

This branch still crashes on startup while generating the timecode lookup tables on Windows, using the nightly I posted to /mess. Looks like I probably didn't fix the thread safety problem properly. (A valgrind run on Linux should immediately point out the problem though.)

Revision history for this message
JWC (jwc) wrote :

I found a feature-bug kind of thing.

When the needle hits the first "warning" groove (which then transitions into the set of grooves for the last minute of the vinyl), I get the following debug message:

Debug: [VinylControlXwax 1]: record end, setting constant mode

(Here's what I mean by the location:)
http://f.jwcxz.com/vinylctrl.jpg

That's a useful feature, but the problem happens after this is triggered. Vinyl control no longer works until I go to preferences, change some audio hardware setting and then change it back (so that the Apply button is enabled) and then click Apply.

I think that what should happen is that the Vinyl Control should be disabled (i.e. in Options -> Vinyl Control, it should be unchecked for that channel). Currently, it remains checked even when switching to constant mode.

Also, it would be nice if this was user-configurable. There's one more minute left in the track after that hits -- if I forget to set the needle back, another minute is usually enough to end the track.

Revision history for this message
Owen Williams (ywwg) wrote :

>
> Debug: [VinylControlXwax 1]: record end, setting constant mode

Your skin may be missing the new vinyl GUI elements. Vinyl control has three modes, which are togglable at any time* with midi or by the GUI: ABSolute, RELative, and CONSTant. Absolute mode follows the needle wherever it goes, relative keeps the playhead in the same place but takes the pitch from the vinyl, and constant just keeps playing at the last rate.

When the needle reaches the end of the record, the vinyl control switches to constant and there's an indicator light that should blink orange. At this point, you can move the needle back a few minutes and then change the playback mode to relative -- this way you can keep controlling the speed of playback.

But, the one bug report I would take from this is that when loading a new track, vinyl control should switch out of CONST and back to either ABS or REL... not sure how best to pick which

(* it is not possible to switch to ABS mode if you're already playing the record -- this prevents accidental sudden jumps. To get to ABS mode, stop the record and then switch the mode)

lp:~ywwg/mixxx/features_xwax2 updated
2555. By Owen Williams <email address hidden>

* when coming out of record-end mode, make sure we're not in constant mode

Revision history for this message
JWC (jwc) wrote :

Thanks for your response, Owen. That makes sense -- I'll wait until the skin gets updated.

One fun little bug, probably the result of a bad merge or something:

Line 218 of src/library/dao/trackdao.cpp needs to be:
                  "filetype, location, comment, url, duration, rating, key, "

The rating and key columns are missing there.

lp:~ywwg/mixxx/features_xwax2 updated
2556. By Owen Williams <email address hidden>

* fixed missing parameters to db call (thanks JWC)

Revision history for this message
Owen Williams (ywwg) wrote :

Ah, now I see the problem, my custom skin got blown away in a merge. I restored the skin.xml now, so try out Outline1024x600-netbook.

lp:~ywwg/mixxx/features_xwax2 updated
2557. By Owen Williams <email address hidden>

* restore custom skin

Revision history for this message
JWC (jwc) wrote :

Ah, with the custom skin, I can easily see what's going on.

However, while disableRecordEndMode() now updates iVCMode at the end of a track, constant mode is still enabled. I added some debug info to disableRecordEndMode() to make it print the value of iVCMode and iOldMode before and after checking to see if we're in constant mode... The result was:

Debug: [VinylControlXwax 1]: record end, setting constant mode
Debug: [VinylControlXwax 1]: disableRecordEndMode called, iVCMode= 2 , iOldMode= 1
Debug: [VinylControlXwax 1]: Switched back, iVCMode= 1 , iOldMode= 1

So, that should mean that for the next track I load (or if I set my needle back and try moving backwards from the end of the current track), I should be in relative mode again. However, it seems to stay in constant mode! I'm guessing that iVCMode gets overwritten again after disableRecordEndMode() gets called?

lp:~ywwg/mixxx/features_xwax2 updated
2558. By Owen Williams <email address hidden>

* stupid bugfix: actually set the mode when disabling record end mode

2559. By Owen Williams <email address hidden>

* merge from lp:mixxx

2560. By Owen Williams <email address hidden>

* fix turning on vinyl control would incorrectly pop out of CONST mode if it was set

2561. By Owen Williams <email address hidden>

* try a new needleskip algorithm
* record-end mode should be used even if needleskip is off

2562. By Owen Williams <email address hidden>

* make sure vinyl control gets the correct sample rate

Revision history for this message
RAFFI TEA (raffitea) wrote :

Great work Owen. Works great on OS X.

Some comments:

* It would be nice to add an "emergency mode". Switch to constant mode if time-code signal becomes bad.
* I like the BPM scaling but it changes too much if the platter is at 0% pitch. We should display the average BPM value over some period of time, let's say 500 ms.

Revision history for this message
RAFFI TEA (raffitea) wrote :

Another potential bug:

If I start in absolute mode and use loops, your code seems to switch to relative mode internally. But this is not reflected on the GUI.

I would have expected some strange behavior if loops are used in absolute mode.

lp:~ywwg/mixxx/features_xwax2 updated
2563. By Owen Williams <email address hidden>

* if looping, don't allow absolute mode control

Revision history for this message
Owen Williams (ywwg) wrote :

@RAFFI_TEA, thanks for the notes. I've fixed the looping bug, so that should be all set now.

The "emergency mode" you describe is already pretty much implemented. Serato vinyl has two pieces of information: pitch and absolute timecode. Mixxx already knows how to deal with situations where we have pitch information but not timecode. As for situations where the pitch data is not audible, it's not really possible to distinguish between "bad signal" and "I lifted the needle." I have some little tricks, like a .1 second delay before I stop playback, and needle-skip detection, but that's about all I can do.

Under what situations are you seeing too much bpm scaling? I have a lot of code to try to make it reasonable already, and last time we talked about this we decided it takes a little getting used to but is actually pretty good.

Revision history for this message
RAFFI TEA (raffitea) wrote :

Hey Owen,

I am struggling a lot with dust in my attic floor -- don't ask me why :-)
As long as I have clean needles the time-code is interpreted fine. But when times goes by the signal becomes rather bad. The pitch jumps up and down.

In Mixxx's preferences, there is a possibility to check the time-code signal. But users won't have preferences opened at all times. Is it possible check the signal quality?

Or in other words, xwax but also commercial product provide a kind of time-code panel (a.k.a. scratch panel in Traktor). There a kind of "circle" if the time-code signal is good. It becomes a kind of dotted ellipse or "egg" if the signal quality decreases. But just before the time-code becomes too bad, an emergency mode (const mode) activates. (Scratch panel: http://www.bonedo.de/typo3temp/pics/4b1809c05e.jpg)

<<<< Under what situations are you seeing too much bpm scaling?
<<<< I have a lot of code to try to make it reasonable already
<<<< and last time we talked about this we decided it takes a
<<<< little gettingused to but is actually pretty good.

At standard pitch, the BPM values alternate between +- 0.4 BPM (assume 128 BPM). I don't hear any noticeable pitch movements through my headphones. I really like the feature but I think BPM values of +- 0.4 are outliers and beginners may be confused to learn beat matching (also they should learn it by ear). Therefore I think some average BPM computation is plausible. (But maybe my turntables need overhaul)

Revision history for this message
Owen Williams (ywwg) wrote :

> BPM values of +- 0.4

These aren't outliers, you're just seeing how much wow/flutter your turntable actually has (I'm already smoothing the values anyway).

Enough people have mentioned this issue that it's probably worth addressing. I've added QTimers in the display widgets that update the BPM displays every 1/4th second. I'd like to strike a balance between updating too fast and too slow.

lp:~ywwg/mixxx/features_xwax2 updated
2564. By Owen Williams <email address hidden>

* only update bpm / rate display every 1/4 second

Revision history for this message
RAFFI TEA (raffitea) wrote :

Thanks, Owen. The BPM display is easier to read now :-)

lp:~ywwg/mixxx/features_xwax2 updated
2565. By Owen Williams <email address hidden>

* use a better method for setting constant mode during needle skip

2566. By Owen Williams <email address hidden>

* make sure to set position no matter what the new scaler is

2567. By Owen Williams <email address hidden>

* merge from lp:mixxx

2568. By Owen Williams <email address hidden>

* merge from lp:mixxx

2569. By Owen Williams <email address hidden>

* merge from lp:mixxx

2570. By Owen Williams <email address hidden>

* make sure table is sorted on startup

2571. By Owen Williams <email address hidden>

* fix sneaky little overflow bug

2572. By Owen Williams <email address hidden>

* introduce new vinyl signal quality widget:
** ring-shaped plot thanks to xwax's built-in functionality
** color representing % of samples returning valid positions: red (0) to green (all)
** a sweep marker, showing direction of vinyl rotation (for left-right switcheroo problems)
** fancy green grid, meaning nothing at all
* note, check XXX: comment about endianness... is this a problem anywhere? (colors will be wrong)
* thanks to Albert for giving me a head-start on this

2573. By Owen Williams <email address hidden>

* redo needleskip checking *again*. I think it's pretty good now but needs testing

2574. By Owen Williams <email address hidden>

* substantial merge from lp:mixxx

2575. By Owen Williams <email address hidden>

* signal widget: if no timer, just draw the bg

2576. By Owen Williams <email address hidden>

* oops

2577. By Owen Williams <email address hidden>

* make steadypitch a little more forgiving again

2578. By Owen Williams <email address hidden>

* only show grid lines if there's a vinyl input that could be receiving a signal

2579. By Owen Williams <email address hidden>

* fix incorrect midi programming for needleskip mode

2580. By Owen Williams <email address hidden>

* merge with lp:mixxx

2581. By Owen Williams <email address hidden>

* merge from lp:mixxx

2582. By Owen Williams <email address hidden>

* remap playposition from -.14 to 1.14 for nicer midi value but still preroll (and eventual postroll)

2583. By Owen Williams <email address hidden>

* support 45RPM vinyl control
* support different vinyl types on each deck
* (note, these changes are not well-tested)

2584. By Owen Williams <email address hidden>

* fix a bug in xwax (a first!) where position wasn't adjusted for 45rpm speeds

2585. By Owen Williams <email address hidden>

* neaten up the vinyl dialog, and add an apply button which redoes the sound setup so user can see changes in the vinyl signal graphs

2586. By Owen Williams <email address hidden>

* add a special version of the vinyl configuration window that is all grayed out for people who don't have vinyl support
* TODO: create a special page on mixxx explaining the app store GPL bullshit concisely, with a big huge link to the
 download page

2587. By Owen Williams <email address hidden>

* fix crash in midiscript: check for NULL before trying to access object

2588. By Owen Williams <email address hidden>

* fix skin

2589. By Owen Williams <email address hidden>

* don't need this

2590. By Owen Williams <email address hidden>

* prevent crashes when no decks have started up

2591. By Owen Williams <email address hidden>

* NEEDS TESTING: first pass at implementing relative mode cue point selection.

2592. By Owen Williams <email address hidden>

* don't seek to inactive hotcues

2593. By Owen Williams <email address hidden>

* merge from lp:mixxx

2594. By Owen Williams <email address hidden>

* merge from lp:mixxx (blows away scratchlib among other things)

2595. By Owen Williams <email address hidden>

* EXPERIMENTAL: initial needle cueing code
* redid an if statement which caused huge changes in tabbing

2596. By Owen Williams <email address hidden>

* added yet another pushbutton, for enabling vinyl cueing mode
* make CDJ resync mode really only active with CDJs

2597. By Owen Williams <email address hidden>

* finish needle cueing mode: now has three possible settings: off, cue only to Cue point, and cue to nearest hotcue

2598. By Owen Williams <email address hidden>

* giant merge from lp:mixxx

2599. By Owen Williams <email address hidden>

* merge from lp:mixxx, hope my linear scale code still works

2600. By Owen Williams <email address hidden>

* whoops

2601. By Owen Williams <email address hidden>

* remove redundant line of extra code that wasn't needed because it was redundant

2602. By Owen Williams <email address hidden>

* fix a bunch of tabbing issues to make the eventual merge cleaner

2603. By Owen Williams <email address hidden>

* asantoni's fixes for building on windows

Revision history for this message
Albert Santoni (gamegod) wrote :

Hey Owen,

The only remaining thing we might want to nitpick in this branch is the changes you made to CachingReader. If CachingReader is supposed to be reading from a file, perhaps it would make more sense for the "if pre-roll then read silence" logic to be outside/before CachingReader instead.

I'm OK if we just leave a note saying maybe there's a better spot for this. I don't think RAMAN nor the scaling classes are the right place for it, so that basically leaves EngineBuffer.

Revision history for this message
Owen Williams (ywwg) wrote :

I'll add the comment for now. I'm not sure how I would move that code out of the reader, because enginebuffer is pretty far removed from the file itself and would have to know exactly how many samples of the buffer should be silence and how many are audio data. This number would depend on: sample rate, playback rate, and if the DJ is reversing direction (scratching the first beat). I'm afraid it would be ugly and bug-prone.

lp:~ywwg/mixxx/features_xwax2 updated
2604. By Owen Williams <email address hidden>

* perhaps preroll code should be somewhere else?

2605. By Owen Williams <email address hidden>

* merge from lp:mixxx

Revision history for this message
RAFFI TEA (raffitea) wrote :

Hey Owen,

tested relative needle cueing. Works great. I really like it. I have never used Scratch Live but now I can imagine how it feels :-) Good work.

Please note that I have not tested the HOT CUE JUMPING!!! This will follow soon.

Revision history for this message
RAFFI TEA (raffitea) wrote :

Just testing the 45RPM seed. Works as expected. No problems during beat-matching.

I've seen a fix for Windows in the revision notes. Can I used your branch on Windows now? No crash during time-code (LUT) initialization?

Revision history for this message
RAFFI TEA (raffitea) wrote :

> Hey Owen,
>
> tested relative needle cueing. Works great. I really like it. I have never
> used Scratch Live but now I can imagine how it feels :-) Good work.
>
> Please note that I have not tested the HOT CUE JUMPING!!! This will follow
> soon.

Hot cue jump = your thirdly implemented option --> jump to the nearest hot cue --> see r2597

Revision history for this message
Albert Santoni (gamegod) wrote :

On Fri, Mar 18, 2011 at 1:10 PM, RAFFI TEA <email address hidden> wrote:
> Just testing the 45RPM seed. Works as expected. No problems during beat-matching.
>
> I've seen a fix for Windows in the revision notes. Can I used your branch on Windows now? No crash during time-code (LUT) initialization?

Correct, this branch works fine for me on Windows now. :)

lp:~ywwg/mixxx/features_xwax2 updated
2606. By Owen Williams <email address hidden>

* merge from lp:mixxx

2607. By Owen Williams <email address hidden>

* don't use keyboard events when class methods will do (solves compiz suckage lp:738881)

2608. By Owen Williams <email address hidden>

* finish implementation of vinyl-controlled track selection
* new method: use control track to select track, then move needle back to regular area and new track will play. Selecting a new track manually will override track selection

2609. By Owen Williams <email address hidden>

* unneeded commented code

Revision history for this message
RAFFI TEA (raffitea) wrote :

> > BPM values of +- 0.4
>
> These aren't outliers, you're just seeing how much wow/flutter your turntable
> actually has (I'm already smoothing the values anyway).
>
> Enough people have mentioned this issue that it's probably worth addressing.
> I've added QTimers in the display widgets that update the BPM displays every
> 1/4th second. I'd like to strike a balance between updating too fast and too
> slow.

Hey Owen,

I am really thankful that BPM updates every 1/4th second. The BPM display is much better to read but it is still not perfectly to read for me. I really recommend to build the average BPM for every time interval. Traktor uses a similar approach which I think is pretty useful to find the perfect pitch position faster.

If you think it is not usefully maybe you can direct me to the code so I can implement for my own :-)

Again, thanks for your awesome work!

Tobias

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

Yea, I was looking at that QTimer suspiciously as I was doing the review :)

I think that instead of doing the updates in the UI (if 4 decks are going,
thats 4x250ms timers = 16Hz GUI updates even if no deck has a loaded track,
we should probably be issuing updates from within the engine. This can be
done in src/engine/bpmcontrol.cpp. They can be rate limited to a certain
window. There is already an engine update interval (e.g. how fast the
position marker on the waveform overview is updated) that we should
piggyback off of.

The added benefit here is that the a MIDI script that updates a BPM LCD on a
MIDI controller (e.g. Stanton SCS1 series) will also update at the same
interval and with the same value.

the file_bpm control is generally the detected BPM of the file and 'bpm'
control is for the engine's current dynamic rate. They are the same
currently, WNumberBpm scales the 'bpm' control by the current rate (there's
an open bug to change that too) but I think they should be changed so that
the 'bpm' control updates in intervals that are the average BPM for the
window in which the rate was calculated.

On Mon, Mar 21, 2011 at 11:03 AM, RAFFI TEA <email address hidden>wrote:

> > > BPM values of +- 0.4
> >
> > These aren't outliers, you're just seeing how much wow/flutter your
> turntable
> > actually has (I'm already smoothing the values anyway).
> >
> > Enough people have mentioned this issue that it's probably worth
> addressing.
> > I've added QTimers in the display widgets that update the BPM displays
> every
> > 1/4th second. I'd like to strike a balance between updating too fast and
> too
> > slow.
>
> Hey Owen,
>
> I am really thankful that BPM updates every 1/4th second. The BPM display
> is much better to read but it is still not perfectly to read for me. I
> really recommend to build the average BPM for every time interval. Traktor
> uses a similar approach which I think is pretty useful to find the perfect
> pitch position faster.
>
> If you think it is not usefully maybe you can direct me to the code so I
> can implement for my own :-)
>
> Again, thanks for your awesome work!
>
> Tobias
> --
> https://code.launchpad.net/~ywwg/mixxx/features_xwax2/+merge/37798
> Your team Mixxx Development Team is subscribed to branch lp:mixxx.
>

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

(the open bug I referenced is here :
https://bugs.launchpad.net/mixxx/+bug/657890)

On Mon, Mar 21, 2011 at 11:27 AM, RJ Ryan <email address hidden> wrote:

> Yea, I was looking at that QTimer suspiciously as I was doing the review :)
>
> I think that instead of doing the updates in the UI (if 4 decks are going,
> thats 4x250ms timers = 16Hz GUI updates even if no deck has a loaded track,
> we should probably be issuing updates from within the engine. This can be
> done in src/engine/bpmcontrol.cpp. They can be rate limited to a certain
> window. There is already an engine update interval (e.g. how fast the
> position marker on the waveform overview is updated) that we should
> piggyback off of.
>
> The added benefit here is that the a MIDI script that updates a BPM LCD on
> a
> MIDI controller (e.g. Stanton SCS1 series) will also update at the same
> interval and with the same value.
>
> the file_bpm control is generally the detected BPM of the file and 'bpm'
> control is for the engine's current dynamic rate. They are the same
> currently, WNumberBpm scales the 'bpm' control by the current rate (there's
> an open bug to change that too) but I think they should be changed so that
> the 'bpm' control updates in intervals that are the average BPM for the
> window in which the rate was calculated.
>
>
> On Mon, Mar 21, 2011 at 11:03 AM, RAFFI TEA <<email address hidden>
> >wrote:
>
> > > > BPM values of +- 0.4
> > >
> > > These aren't outliers, you're just seeing how much wow/flutter your
> > turntable
> > > actually has (I'm already smoothing the values anyway).
> > >
> > > Enough people have mentioned this issue that it's probably worth
> > addressing.
> > > I've added QTimers in the display widgets that update the BPM displays
> > every
> > > 1/4th second. I'd like to strike a balance between updating too fast
> and
> > too
> > > slow.
> >
> > Hey Owen,
> >
> > I am really thankful that BPM updates every 1/4th second. The BPM display
> > is much better to read but it is still not perfectly to read for me. I
> > really recommend to build the average BPM for every time interval.
> Traktor
> > uses a similar approach which I think is pretty useful to find the
> perfect
> > pitch position faster.
> >
> > If you think it is not usefully maybe you can direct me to the code so I
> > can implement for my own :-)
> >
> > Again, thanks for your awesome work!
> >
> > Tobias
> > --
> > https://code.launchpad.net/~ywwg/mixxx/features_xwax2/+merge/37798
> > Your team Mixxx Development Team is subscribed to branch lp:mixxx.
> >
>
> --
> https://code.launchpad.net/~ywwg/mixxx/features_xwax2/+merge/37798
> Your team Mixxx Development Team is subscribed to branch lp:mixxx.
>

lp:~ywwg/mixxx/features_xwax2 updated
2610. By Owen Williams <email address hidden>

* tweaks to control track selection to make it better

2611. By Owen Williams <email address hidden>

* merge from lp:mixxx

2612. By Owen Williams <email address hidden>

* merge cleanup

2613. By Owen Williams <email address hidden>

* merge from lp:mixxx

2614. By Owen Williams <email address hidden>

* fix linear scaler pops and clicks once and for all
* disable vinyl emu in enginechannel because it hurts more than it helps

2615. By Owen Williams <email address hidden>

* comment debug statement

2616. By Owen Williams <email address hidden>

* no really, linear scaler is probably better than serato's now

2617. By Owen Williams <email address hidden>

* merge from lp:mixxx

Revision history for this message
RAFFI TEA (raffitea) wrote :

Tested with Traktor vinyls. Works great! Don't feel any differences compared to Serato.

I also used Serato and Traktor vinyls simultaneously on my turntables. Works as expected.

Why don't we support Traktor CDs? --Xwax limitation?

lp:~ywwg/mixxx/features_xwax2 updated
2618. By Owen Williams <email address hidden>

* merge from lp:mixxx

2619. By Owen Williams <email address hidden>

* fix building on windows, or so asantoni thinks

2620. By Owen Williams <email address hidden>

* merge from lp:mixx

Revision history for this message
JWC (jwc) wrote :

Found a bug! I think it's the result of a problem with the merging and all the recent changes.

If you press a hotcue several times quickly while vinyl control is playing the record, the "play" button will remain in the "playing" state indefinitely (really, I tried everything to get it to shut off), meaning that you can't load a new track, so you have to restart Mixxx.

Revision history for this message
RAFFI TEA (raffitea) wrote :

> Found a bug! I think it's the result of a problem with the merging and all
> the recent changes.
>
> If you press a hotcue several times quickly while vinyl control is playing the
> record, the "play" button will remain in the "playing" state indefinitely
> (really, I tried everything to get it to shut off), meaning that you can't
> load a new track, so you have to restart Mixxx.

Found another minor bug. Try to set a loop and look at the play button while the loop is active. The play button goes off and on ("blinks") but the music just plays fine.

lp:~ywwg/mixxx/features_xwax2 updated
2621. By Owen Williams <email address hidden>

* merge from lp:mixxx

2622. By Owen Williams <email address hidden>

* fix play button turning off while looping
* clean up some unused variables

2623. By Owen Williams <email address hidden>

* merge from lp:mixxx

2624. By Owen Williams <email address hidden>

* updated xwax safe values for traktor (thanks Tobias!)

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :
Download full text (8.8 KiB)

Hey Owen,

I read over your changes and I have some comments and things that need fixing. I covered things that weren't src/vinyl* classes. I'm going to leave those to Albert. This branch is a huge amount of work so thanks so much for your dedication to making Mixxx vinyl control rock!

First off, I'm going to be picky about control naming :) It's
important because by publishing the controls, we're going to be
stuck with the names for years to come, so I'd like them to be
descriptive and uniform. Also, MIDI scripters and GUI skinners
are going to have to deal w/ them, so it's important that they
be descriptive and easy to use.

* Naming of control values should be lowercase separated by
  underscores on word boundaries (most controls are formatted
  this way). Optionally prefix it with a general identifier like
  'vinylcontrol' or 'vc' if the controls appear in a group not
  specific to the feature like [VinylControl].

* For controls in [ChannelX]
-- vinylcontrol_enabled
-- vinylcontrol_mode
-- vinylcontrol_cueing
-- vinylcontrol_vinyl_type
-- vinylcontrol_speed_type // _type so its not confused w/ a numerical value. you can also keep str if you want, but really its a type that happens to have a string associated with.

* For controls in the group [VinylControl]
-- lead_in_time
-- mode
-- needle_skip_prevention
-- gain

Hopefully this isn't a huge deal and you can just find-replace all the instances. Let me know if I'm being a huge pain in the ass.

controlpushbutton.cpp
---------------------

* In ControlPushButton::ControlPushButton(ConfigKey), m_dValue is
  set to 0 in ControlObject constructor, redundant

* I think a CPB w/ multiple states should be a "toggle" button
  with multiple states. Non-toggle should remain a momentary 0 or
  1 button. Does that screw anything up? Just move the check in
  setvalueFromMidi from the !m_bIsToggleButton block to the
  m_bIsToggleButton block. I think all you should have to do is
  setToggle(true) for the vinyl cue and mode controls in
  RateControl.

mathstuff.h
-----------

* Super-yes on adding the isnan stuff to this file. Though why is
  is the non-Windows #include of math.h commented?

mixxx.cpp
---------

* This config initialization / saving code, along with all the
  code in SoundManager, should be migrated to a
  VinylControlManager class that lives in MixxxApp. Can be done
  post-merge though.

widget/wnumberpos.cpp
widget/woverview.cpp
---------------------

* I'm pretty shaky on these changes (the range ajustments). I
  think it might help me if I explain the entire path the
  playposition control travels from CO to the widget:

* playposition is a ControlPotmeter and it has a range of -.14 to
  1.14. Let's consider the case that playposition is 0 (a track
  has just been loaded)

* When playposition is set to a value, an update for its proxies is queued.

* On a CO::sync(), this update is propagated to the proxies via
  COT::setExtern.

* All GUI widgets mapped to the skin have a
  ControlObjectThreadWidget attached to them. On an update to a
  COTW, COTW::setExtern is called. This posts a Qt event to the
  COTM (base class) with the value from
  CO::getValueToWidget(value), where val...

Read more...

review: Needs Fixing
lp:~ywwg/mixxx/features_xwax2 updated
2625. By Owen Williams <email address hidden>

* change ALL vinyl control object names
* requires skin and config file updates

2626. By Owen Williams <email address hidden>

* typo in control object name

2627. By Owen Williams <email address hidden>

* make multistate pushbottons toggling

2628. By Owen Williams <email address hidden>

* revert numberrate, don't do throttling here

2629. By Owen Williams <email address hidden>

* fix typo

2630. By Owen Williams <email address hidden>

* get rid of special buffers for scratching, just use the buffer we have

2631. By Owen Williams <email address hidden>

* little tiny code-neatening

2632. By Owen Williams <email address hidden>

* fix some memory leaks in status light code

2633. By Owen Williams <email address hidden>

* merge from lp:mixxx

Revision history for this message
JWC (jwc) wrote :

*sigh* This is an interesting one.

Start playing a track at any tempo. Then, spin the vinyl back and release it to let it play. For about a quarter of a second, it'll play correctly at the original tempo, and then right afterwards, the pitch will dip very heavily (>10%) and return back to normal about 50% later.

You can also achieve this effect by scratching back and forth, but the _speed_ at which you scratch actually makes a difference. For example, if you scratch back and forth at roughly 33rpm, the pitch dip won't exist! If you scratch slowly though, the amount that the pitch dips seems to be proportional to the speed at which you scroll.

Again, the weird thing is that the record starts playing normally for about a quarter of a second and _then_ the pitch dips and returns back to normal.

Yes, I verified that this is not in fact my turntables.

Revision history for this message
RAFFI TEA (raffitea) wrote :

JWC,

sounds like we have similar problems, but the only difference is, that I experience the problem with Traktor Scratch, too. Whenever I release the vinyl on my (crappy?) "Reloop RR 4000 m3d" the BPM value increases up to 5% and more. After a few seconds the BPM values narrows down to the base value. This is the reason why I always use "key lock" to prevent changes in the pitch.

Please note that I used clean needles and Serato/Traktor records for the test.

I guess that my turntables are crappy. Would be interested how a Tecnics behave... because the behavior of my turntables is not normal. All professional DJs I know don't have such problems.

Revision history for this message
JWC (jwc) wrote :

I forgot to mention that I am using CV02 vinyls that are pretty clean and two needles that are fine. My turntables aren't great: Stanton STR8-60 and T.80. But looking at the samples on return from the backspin or whatever, they look okay. I'm thinking it's an error in the algorithm. Somehow, information recovery doesn't seem work quite correctly. Specifically, I'm extremely interested in figuring out why after a backspin, the sound returns normally for a quarter of a second before the dip in pitch.

I'll record some examples in a few hours for the purposes of demonstration.

Revision history for this message
JWC (jwc) wrote :

Possible reason (haven't tested to confirm yet):

Last night I was playing with my main sound card as output and my usb sound card as input. Normally, I use my USB card as both input and output. For my laptop, though, the USB bus doesn't seem to have enough bandwidth to do this.

So, radiomark told me on IRC that the samples weren't syncing up because of clock differences:

> radiomark | If you imagine, it has to synchronise playback (which runs
> | from the soundcard's outbound clock) with the relative and
> | absolute vinyl data (which runs on the clock of the spinnin
> | vinyl)

I'm investigating this a bit. Maybe you already implemented it and it's something else... I'll take a look.

lp:~ywwg/mixxx/features_xwax2 updated
2634. By Owen Williams <email address hidden>

* merge from lp:mixxx

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

OK looks like all my complaints are now fixed :) (minus VinylControlControl in EngineBuffer -- I'll remove that myself post merge) it's now up to albert to OK the vinylcontrol* classes and then we can merge this sucker.

Just to get it in writing somewhere, ywwg and I talked about how I asked him to use WDisplay instead of WStatusLight, but it turns out WDisplay is geared towards being a knob but with no input. The control connected to it is scaled to 0.0 through 128.0 based on its range. This isn't suitable for indicator controls that represent fixed status values.

After Owen's changes, WStatusLight will be to WPushButton what WDisplay is to WKnob.

review: Approve
lp:~ywwg/mixxx/features_xwax2 updated
2635. By Owen Williams <email address hidden>

* merge from lp:mixxx

Revision history for this message
Albert Santoni (gamegod) wrote :

I also approve. The vinyl control logic is fairly straightforward, it
just needs loads of testing now to make sure it's tight. Some final
comments:
- I would squash those qDebugs in vinylcontrolxwax.cpp before the release.
- Maybe factor some of those constants out too.
- We should make sure a tester verifies that vinyl control still works
with a 96000 Hz samplerate.

Otherwise, great stuff! Launchpad's code review interface is broken
right now so I can't approve it properly, but RJ, feel free to merge
into trunk whenever.

Thanks Owen!
Albert

On Fri, Apr 8, 2011 at 11:21 PM, RJ Ryan <email address hidden> wrote:
> Review: Approve
> OK looks like all my complaints are now fixed :) (minus VinylControlControl in EngineBuffer -- I'll remove that myself post merge) it's now up to albert to OK the vinylcontrol* classes and then we can merge this sucker.
>
> Just to get it in writing somewhere, ywwg and I talked about how I asked him to use WDisplay instead of WStatusLight, but it turns out WDisplay is geared towards being a knob but with no input. The control connected to it is scaled to 0.0 through 128.0 based on its range. This isn't suitable for indicator controls that represent fixed status values.
>
> After Owen's changes, WStatusLight will be to WPushButton what WDisplay is to WKnob.
> --
> https://code.launchpad.net/~ywwg/mixxx/features_xwax2/+merge/37798
> You are reviewing the proposed merge of lp:~ywwg/mixxx/features_xwax2 into lp:mixxx.
>

lp:~ywwg/mixxx/features_xwax2 updated
2636. By Owen Williams <email address hidden>

* merge from lp:mixxx

2637. By Owen Williams <email address hidden>

* merge bill's trunk merge fixes from lp:~mixxxdevelopers/mixxx/features_xwax2_bill

2638. By Owen Williams <email address hidden>

* merge from lp:mixxx

Revision history for this message
William Good (bkgood) wrote :

Can we hold off on merging this for a bit longer? I'm working though getting the code to conform (at least mostly) with style guidelines, mostly tabs to spaces and deleting characters matching \b+$ (so whitespace at end of lines), but perhaps more importantly some unbraced if's (which can create real confusion later).

review: Disapprove
lp:~ywwg/mixxx/features_xwax2 updated
2639. By Owen Williams <email address hidden>

* merge from lp:~mixxxdevelopers/mixxx/features_xwax2_bill

2640. By Owen Williams <email address hidden>

* merge from lp:~mixxxdevelopers/mixxx/features_xwax2_bill (cleanup)

Revision history for this message
William Good (bkgood) wrote :

heh, I lost it too :) It looks like you got all my changes, so I'm happy. Merge away!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'mixxx/build/depends.py'
2--- mixxx/build/depends.py 2011-04-13 02:58:05 +0000
3+++ mixxx/build/depends.py 2011-04-16 23:24:33 +0000
4@@ -336,6 +336,7 @@
5 "dlgprefcontrols.cpp",
6 "dlgprefbpm.cpp",
7 "dlgprefreplaygain.cpp",
8+ "dlgprefnovinyl.cpp",
9 "dlgbpmscheme.cpp",
10 "dlgabout.cpp",
11 "dlgprefeq.cpp",
12@@ -581,6 +582,7 @@
13 build.env.Uic4('dlgbpmschemedlg.ui')
14 # build.env.Uic4('dlgbpmtapdlg.ui')
15 build.env.Uic4('dlgprefvinyldlg.ui')
16+ build.env.Uic4('dlgprefnovinyldlg.ui')
17 build.env.Uic4('dlgprefrecorddlg.ui')
18 build.env.Uic4('dlgaboutdlg.ui')
19 build.env.Uic4('dlgmidilearning.ui')
20
21=== modified file 'mixxx/build/features.py'
22--- mixxx/build/features.py 2011-04-04 05:46:49 +0000
23+++ mixxx/build/features.py 2011-04-16 23:24:33 +0000
24@@ -293,9 +293,12 @@
25 'dlgprefvinyl.cpp',
26 'vinylcontrolsignalwidget.cpp']
27 if build.platform_is_windows:
28- sources.append("#lib/xwax/timecoder_win32.c")
29+ sources.append("#lib/xwax/timecoder_win32.cpp")
30+ sources.append("#lib/xwax/lut.cpp")
31 else:
32 sources.append("#lib/xwax/timecoder.c")
33+ sources.append("#lib/xwax/lut.c")
34+
35 return sources
36
37 class Tonal(Feature):
38
39=== modified file 'mixxx/build/qtcreator/mixxx.pro'
40--- mixxx/build/qtcreator/mixxx.pro 2010-11-11 19:10:52 +0000
41+++ mixxx/build/qtcreator/mixxx.pro 2011-04-16 23:24:33 +0000
42@@ -110,6 +110,7 @@
43 $$UI_DIR/ui_dlgprefrecorddlg.h \
44 $$UI_DIR/ui_dlgprefsounddlg.h \
45 $$UI_DIR/ui_dlgprefvinyldlg.h \
46+ $$UI_DIR/ui_dlgprefnovinyldlg.h \
47 $$UI_DIR/ui_dlgprefnomididlg.h
48
49 INCLUDEPATH += src \
50
51=== added file 'mixxx/lib/xwax/lut.c'
52--- mixxx/lib/xwax/lut.c 1970-01-01 00:00:00 +0000
53+++ mixxx/lib/xwax/lut.c 2011-04-16 23:24:33 +0000
54@@ -0,0 +1,110 @@
55+/*
56+ * Copyright (C) 2010 Mark Hills <mark@pogo.org.uk>
57+ *
58+ * This program is free software; you can redistribute it and/or
59+ * modify it under the terms of the GNU General Public License
60+ * version 2, as published by the Free Software Foundation.
61+ *
62+ * This program is distributed in the hope that it will be useful, but
63+ * WITHOUT ANY WARRANTY; without even the implied warranty of
64+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
65+ * General Public License version 2 for more details.
66+ *
67+ * You should have received a copy of the GNU General Public License
68+ * version 2 along with this program; if not, write to the Free
69+ * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
70+ * MA 02110-1301, USA.
71+ *
72+ */
73+
74+#include <stdio.h>
75+#include <stdlib.h>
76+
77+#include "lut.h"
78+
79+/* The number of bits to form the hash, which governs the overall size
80+ * of the hash lookup table, and hence the amount of chaining */
81+
82+#define HASH_BITS 16
83+
84+#define HASH(timecode) ((timecode) & ((1 << HASH_BITS) - 1))
85+#define NO_SLOT ((unsigned)-1)
86+
87+
88+/* Initialise an empty hash lookup table to store the given number
89+ * of timecode -> position lookups */
90+
91+int lut_init(struct lut_t *lut, int nslots)
92+{
93+ int n, hashes;
94+ size_t bytes;
95+
96+ hashes = 1 << HASH_BITS;
97+ bytes = sizeof(struct slot_t) * nslots + sizeof(slot_no_t) * hashes;
98+
99+ fprintf(stderr, "Lookup table has %d hashes to %d slots"
100+ " (%d slots per hash, %zuKb)\n",
101+ hashes, nslots, nslots / hashes, bytes / 1024);
102+
103+ lut->slot = (struct slot_t*)malloc(sizeof(struct slot_t) * nslots);
104+ if (lut->slot == NULL) {
105+ perror("malloc");
106+ return -1;
107+ }
108+
109+ lut->table = (slot_no_t*)malloc(sizeof(slot_no_t) * hashes);
110+ if (lut->table == NULL) {
111+ perror("malloc");
112+ return -1;
113+ }
114+
115+ for (n = 0; n < hashes; n++)
116+ lut->table[n] = NO_SLOT;
117+
118+ lut->avail = 0;
119+
120+ return 0;
121+}
122+
123+
124+void lut_clear(struct lut_t *lut)
125+{
126+ free(lut->table);
127+}
128+
129+
130+void lut_push(struct lut_t *lut, unsigned int timecode)
131+{
132+ unsigned int hash;
133+ slot_no_t slot_no;
134+ struct slot_t *slot;
135+
136+ slot_no = lut->avail++; /* take the next available slot */
137+
138+ slot = &lut->slot[slot_no];
139+ slot->timecode = timecode;
140+
141+ hash = HASH(timecode);
142+ slot->next = lut->table[hash];
143+ lut->table[hash] = slot_no;
144+}
145+
146+
147+unsigned int lut_lookup(struct lut_t *lut, unsigned int timecode)
148+{
149+ unsigned int hash;
150+ slot_no_t slot_no;
151+ struct slot_t *slot;
152+
153+ hash = HASH(timecode);
154+ slot_no = lut->table[hash];
155+
156+ while (slot_no != NO_SLOT) {
157+ slot = &lut->slot[slot_no];
158+ if (slot->timecode == timecode)
159+ return slot_no;
160+ slot_no = slot->next;
161+ }
162+
163+ return (unsigned)-1;
164+}
165
166=== added file 'mixxx/lib/xwax/lut.cpp'
167--- mixxx/lib/xwax/lut.cpp 1970-01-01 00:00:00 +0000
168+++ mixxx/lib/xwax/lut.cpp 2011-04-16 23:24:33 +0000
169@@ -0,0 +1,110 @@
170+/*
171+ * Copyright (C) 2010 Mark Hills <mark@pogo.org.uk>
172+ *
173+ * This program is free software; you can redistribute it and/or
174+ * modify it under the terms of the GNU General Public License
175+ * version 2, as published by the Free Software Foundation.
176+ *
177+ * This program is distributed in the hope that it will be useful, but
178+ * WITHOUT ANY WARRANTY; without even the implied warranty of
179+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
180+ * General Public License version 2 for more details.
181+ *
182+ * You should have received a copy of the GNU General Public License
183+ * version 2 along with this program; if not, write to the Free
184+ * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
185+ * MA 02110-1301, USA.
186+ *
187+ */
188+
189+#include <stdio.h>
190+#include <stdlib.h>
191+
192+#include "lut.h"
193+
194+/* The number of bits to form the hash, which governs the overall size
195+ * of the hash lookup table, and hence the amount of chaining */
196+
197+#define HASH_BITS 16
198+
199+#define HASH(timecode) ((timecode) & ((1 << HASH_BITS) - 1))
200+#define NO_SLOT ((unsigned)-1)
201+
202+
203+/* Initialise an empty hash lookup table to store the given number
204+ * of timecode -> position lookups */
205+
206+int lut_init(struct lut_t *lut, int nslots)
207+{
208+ int n, hashes;
209+ size_t bytes;
210+
211+ hashes = 1 << HASH_BITS;
212+ bytes = sizeof(struct slot_t) * nslots + sizeof(slot_no_t) * hashes;
213+
214+ fprintf(stderr, "Lookup table has %d hashes to %d slots"
215+ " (%d slots per hash, %zuKb)\n",
216+ hashes, nslots, nslots / hashes, bytes / 1024);
217+
218+ lut->slot = (struct slot_t*)malloc(sizeof(struct slot_t) * nslots);
219+ if (lut->slot == NULL) {
220+ perror("malloc");
221+ return -1;
222+ }
223+
224+ lut->table = (slot_no_t*)malloc(sizeof(slot_no_t) * hashes);
225+ if (lut->table == NULL) {
226+ perror("malloc");
227+ return -1;
228+ }
229+
230+ for (n = 0; n < hashes; n++)
231+ lut->table[n] = NO_SLOT;
232+
233+ lut->avail = 0;
234+
235+ return 0;
236+}
237+
238+
239+void lut_clear(struct lut_t *lut)
240+{
241+ free(lut->table);
242+}
243+
244+
245+void lut_push(struct lut_t *lut, unsigned int timecode)
246+{
247+ unsigned int hash;
248+ slot_no_t slot_no;
249+ struct slot_t *slot;
250+
251+ slot_no = lut->avail++; /* take the next available slot */
252+
253+ slot = &lut->slot[slot_no];
254+ slot->timecode = timecode;
255+
256+ hash = HASH(timecode);
257+ slot->next = lut->table[hash];
258+ lut->table[hash] = slot_no;
259+}
260+
261+
262+unsigned int lut_lookup(struct lut_t *lut, unsigned int timecode)
263+{
264+ unsigned int hash;
265+ slot_no_t slot_no;
266+ struct slot_t *slot;
267+
268+ hash = HASH(timecode);
269+ slot_no = lut->table[hash];
270+
271+ while (slot_no != NO_SLOT) {
272+ slot = &lut->slot[slot_no];
273+ if (slot->timecode == timecode)
274+ return slot_no;
275+ slot_no = slot->next;
276+ }
277+
278+ return (unsigned)-1;
279+}
280
281=== added file 'mixxx/lib/xwax/lut.h'
282--- mixxx/lib/xwax/lut.h 1970-01-01 00:00:00 +0000
283+++ mixxx/lib/xwax/lut.h 2011-04-16 23:24:33 +0000
284@@ -0,0 +1,42 @@
285+/*
286+ * Copyright (C) 2009 Mark Hills <mark@pogo.org.uk>
287+ *
288+ * This program is free software; you can redistribute it and/or
289+ * modify it under the terms of the GNU General Public License
290+ * version 2, as published by the Free Software Foundation.
291+ *
292+ * This program is distributed in the hope that it will be useful, but
293+ * WITHOUT ANY WARRANTY; without even the implied warranty of
294+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
295+ * General Public License version 2 for more details.
296+ *
297+ * You should have received a copy of the GNU General Public License
298+ * version 2 along with this program; if not, write to the Free
299+ * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
300+ * MA 02110-1301, USA.
301+ *
302+ */
303+
304+#ifndef LUT_H
305+#define LUT_H
306+
307+typedef unsigned int slot_no_t;
308+
309+struct slot_t {
310+ unsigned int timecode;
311+ slot_no_t next; /* next slot with the same hash */
312+};
313+
314+struct lut_t {
315+ struct slot_t *slot;
316+ slot_no_t *table, /* hash -> slot lookup */
317+ avail; /* next available slot */
318+};
319+
320+int lut_init(struct lut_t *lut, int nslots);
321+void lut_clear(struct lut_t *lut);
322+
323+void lut_push(struct lut_t *lut, unsigned int timecode);
324+unsigned int lut_lookup(struct lut_t *lut, unsigned int timecode);
325+
326+#endif
327
328=== added file 'mixxx/lib/xwax/pitch.h'
329--- mixxx/lib/xwax/pitch.h 1970-01-01 00:00:00 +0000
330+++ mixxx/lib/xwax/pitch.h 2011-04-16 23:24:33 +0000
331@@ -0,0 +1,71 @@
332+/*
333+ * Copyright (C) 2010 Mark Hills <mark@pogo.org.uk>
334+ *
335+ * This program is free software; you can redistribute it and/or
336+ * modify it under the terms of the GNU General Public License
337+ * version 2, as published by the Free Software Foundation.
338+ *
339+ * This program is distributed in the hope that it will be useful, but
340+ * WITHOUT ANY WARRANTY; without even the implied warranty of
341+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
342+ * General Public License version 2 for more details.
343+ *
344+ * You should have received a copy of the GNU General Public License
345+ * version 2 along with this program; if not, write to the Free
346+ * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
347+ * MA 02110-1301, USA.
348+ *
349+ */
350+
351+#ifndef PITCH_H
352+#define PITCH_H
353+
354+/* Values for the filter concluded experimentally */
355+
356+#define ALPHA (1.0/512)
357+#define BETA (ALPHA/1024)
358+
359+/* State of the pitch calculation filter */
360+
361+struct pitch_t {
362+ float dt, x, v;
363+};
364+
365+/* Prepare the filter for observations every dt seconds */
366+
367+static inline void pitch_init(struct pitch_t *p, float dt)
368+{
369+ p->dt = dt;
370+ p->x = 0.0;
371+ p->v = 0.0;
372+}
373+
374+/* Input an observation to the filter; in the last dt seconds the
375+ * position has moved by dx.
376+ *
377+ * Because the vinyl uses timestamps, the values for dx are discrete
378+ * rather than smooth. */
379+
380+static inline void pitch_dt_observation(struct pitch_t *p, float dx)
381+{
382+ float predicted_x, predicted_v, residual_x;
383+
384+ predicted_x = p->x + p->v * p->dt;
385+ predicted_v = p->v;
386+
387+ residual_x = dx - predicted_x;
388+
389+ p->x = predicted_x + residual_x * ALPHA;
390+ p->v = predicted_v + residual_x * BETA / p->dt;
391+
392+ p->x -= dx; /* relative to previous */
393+}
394+
395+/* Get the pitch after filtering */
396+
397+static inline float pitch_current(struct pitch_t *p)
398+{
399+ return p->v;
400+}
401+
402+#endif
403
404=== modified file 'mixxx/lib/xwax/timecoder.c'
405--- mixxx/lib/xwax/timecoder.c 2011-04-08 06:04:27 +0000
406+++ mixxx/lib/xwax/timecoder.c 2011-04-16 23:24:33 +0000
407@@ -1,5 +1,5 @@
408 /*
409- * Copyright (C) 2008 Mark Hills <mark@pogo.org.uk>
410+ * Copyright (C) 2010 Mark Hills <mark@pogo.org.uk>
411 *
412 * This program is free software; you can redistribute it and/or
413 * modify it under the terms of the GNU General Public License
414@@ -17,6 +17,7 @@
415 *
416 */
417
418+#include <assert.h>
419 #include <stdio.h>
420 #include <stdlib.h>
421 #include <string.h>
422@@ -25,10 +26,8 @@
423 #include "timecoder.h"
424
425 #define ZERO_THRESHOLD 128
426-#define SIGNAL_THRESHOLD 256
427
428-#define ZERO_AVG 1024
429-#define SIGNAL_AVG 256
430+#define ZERO_RC 0.001 /* time constant for zero/rumble filter */
431
432 #define REF_PEAKS_AVG 48 /* in wave cycles */
433
434@@ -45,187 +44,201 @@
435
436 /* Timecode definitions */
437
438-
439-#define POLARITY_NEGATIVE 0
440-#define POLARITY_POSITIVE 1
441-
442-
443-struct timecode_def_t timecode_def[] = {
444- {
445- name: "serato_2a",
446- desc: "Serato 2nd Ed., side A",
447- resolution: 1000,
448- polarity: POLARITY_POSITIVE,
449- bits: 20,
450- seed: 0x59017,
451- tap: {2, 5, 6, 7, 8, 13, 14, 16, 17},
452- ntaps: 9,
453- length: 712000,
454- safe: 707000,
455- lookup: NULL
456- },
457- {
458- name: "serato_2b",
459- desc: "Serato 2nd Ed., side B",
460- seed: 0x8f3c6,
461- resolution: 1000,
462- polarity: POLARITY_POSITIVE,
463- bits: 20,
464- tap: {3, 4, 6, 7, 12, 13, 14, 15, 18}, /* reverse of side A */
465- ntaps: 9,
466- length: 922000,
467- safe: 917000,
468- lookup: NULL
469- },
470- {
471- name: "serato_cd",
472- desc: "Serato CD",
473- resolution: 1000,
474- polarity: POLARITY_POSITIVE,
475- bits: 20,
476- seed: 0xd8b40,
477- tap: {2, 4, 6, 8, 10, 11, 14, 16, 17},
478- ntaps: 9,
479- length: 910000,
480- safe: 900000,
481- lookup: NULL
482- },
483- {
484- name: "traktor_a",
485- desc: "Traktor Scratch, side A",
486- resolution: 2000,
487- polarity: POLARITY_POSITIVE,
488- bits: 23,
489- seed: 0x134503,
490- tap: {6, 12, 18},
491- ntaps: 3,
492- length: 1500000,
493- safe: 1480000,
494- lookup: NULL
495- },
496- {
497- name: "traktor_b",
498- desc: "Traktor Scratch, side B",
499- resolution: 2000,
500- polarity: POLARITY_POSITIVE,
501- bits: 23,
502- seed: 0x32066c,
503- tap: {6, 12, 18},
504- ntaps: 3,
505- length: 2110000,
506- safe: 2090000,
507- lookup: NULL
508- },
509- {
510- name: NULL
511+#define SWITCH_PHASE 0x1 /* tone phase difference of 270 (not 90) degrees */
512+#define SWITCH_PRIMARY 0x2 /* use left channel (not right) as primary */
513+#define SWITCH_POLARITY 0x4 /* read bit values in negative (not positive) */
514+
515+
516+static struct timecode_def_t timecode_def[] = {
517+ {
518+ .name = "serato_2a",
519+ .desc = "Serato 2nd Ed., side A",
520+ .resolution = 1000,
521+ .flags = 0,
522+ .bits = 20,
523+ .seed = 0x59017,
524+ .taps = 0x361e4,
525+ .length = 712000,
526+ .safe = 625000,
527+ .lookup = false
528+ },
529+ {
530+ .name = "serato_2b",
531+ .desc = "Serato 2nd Ed., side B",
532+ .resolution = 1000,
533+ .flags = 0,
534+ .bits = 20,
535+ .seed = 0x8f3c6,
536+ .taps = 0x4f0d8, /* reverse of side A */
537+ .length = 922000,
538+ .safe = 905000,
539+ .lookup = false
540+ },
541+ {
542+ .name = "serato_cd",
543+ .desc = "Serato CD",
544+ .resolution = 1000,
545+ .flags = 0,
546+ .bits = 20,
547+ .seed = 0xd8b40,
548+ .taps = 0x34d54,
549+ .length = 950000,
550+ .safe = 940000,
551+ .lookup = false
552+ },
553+ {
554+ .name = "traktor_a",
555+ .desc = "Traktor Scratch, side A",
556+ .resolution = 2000,
557+ .flags = SWITCH_PRIMARY | SWITCH_POLARITY | SWITCH_PHASE,
558+ .bits = 23,
559+ .seed = 0x134503,
560+ .taps = 0x041040,
561+ .length = 1500000,
562+ .safe = 1463000,
563+ .lookup = false
564+ },
565+ {
566+ .name = "traktor_b",
567+ .desc = "Traktor Scratch, side B",
568+ .resolution = 2000,
569+ .flags = SWITCH_PRIMARY | SWITCH_POLARITY | SWITCH_PHASE,
570+ .bits = 23,
571+ .seed = 0x32066c,
572+ .taps = 0x041040, /* same as side A */
573+ .length = 2110000,
574+ .safe = 2068000,
575+ .lookup = false
576+ },
577+ {
578+ .name = "mixvibes_v2",
579+ .desc = "MixVibes V2",
580+ .resolution = 1300,
581+ .flags = SWITCH_PHASE,
582+ .bits = 20,
583+ .seed = 0x22c90,
584+ .taps = 0x00008,
585+ .length = 950000,
586+ .safe = 923000,
587+ .lookup = false
588+ },
589+ {
590+ .name = "mixvibes_7inch",
591+ .desc = "MixVibes 7\"",
592+ .resolution = 1300,
593+ .flags = SWITCH_PHASE,
594+ .bits = 20,
595+ .seed = 0x22c90,
596+ .taps = 0x00008,
597+ .length = 312000,
598+ .safe = 310000,
599+ .lookup = false
600+ },
601+ {
602+ .name = NULL
603 }
604 };
605
606
607-//struct timecode_def_t *def;
608-
609-
610 /* Linear Feeback Shift Register in the forward direction. New values
611 * are generated at the least-significant bit. */
612
613-static inline int lfsr(unsigned int code, struct timecoder_t *timecoder)
614-{
615- unsigned int r;
616- char s, n;
617-
618- r = code & 1;
619-
620- for(n = 0; n < timecoder->tc_table->ntaps; n++) {
621- s = *(timecoder->tc_table->tap + n);
622- r += (code & (1 << s)) >> s;
623- }
624-
625- return r & 0x1;
626-}
627-
628-
629-/* Linear Feeback Shift Register in the reverse direction. New values
630- * are generated at the most-significant bit. */
631-
632-static inline int lfsr_rev(unsigned int code, struct timecoder_t *timecoder)
633-{
634- unsigned int r;
635- char s, n;
636-
637- r = (code & (1 << (timecoder->tc_table->bits - 1))) >> (timecoder->tc_table->bits - 1);
638-
639- for(n = 0; n < timecoder->tc_table->ntaps; n++) {
640- s = *(timecoder->tc_table->tap + n) - 1;
641- r += (code & (1 << s)) >> s;
642- }
643-
644- return r & 0x1;
645-}
646-
647-
648-/* Setup globally, for a chosen timecode definition */
649-
650-int timecoder_build_lookup(char *timecode_name, struct timecoder_t *timecoder) {
651- unsigned int n, current;
652-
653+static inline bits_t lfsr(bits_t code, bits_t taps)
654+{
655+ bits_t taken;
656+ int xrs;
657+
658+ taken = code & taps;
659+ xrs = 0;
660+ while (taken != 0x0) {
661+ xrs += taken & 0x1;
662+ taken >>= 1;
663+ }
664+
665+ return xrs & 0x1;
666+}
667+
668+
669+static inline bits_t fwd(bits_t current, struct timecode_def_t *def)
670+{
671+ bits_t l;
672+
673+ /* New bits are added at the MSB; shift right by one */
674+
675+ l = lfsr(current, def->taps | 0x1);
676+ return (current >> 1) | (l << (def->bits - 1));
677+}
678+
679+
680+static inline bits_t rev(bits_t current, struct timecode_def_t *def)
681+{
682+ bits_t l, mask;
683+
684+ /* New bits are added at the LSB; shift left one and mask */
685+
686+ mask = (1 << def->bits) - 1;
687+ l = lfsr(current, (def->taps >> 1) | (0x1 << (def->bits - 1)));
688+ return ((current << 1) & mask) | l;
689+}
690+
691+
692+static struct timecode_def_t* find_definition(const char *name)
693+{
694 struct timecode_def_t *def;
695+
696 def = &timecode_def[0];
697-
698- while(def->name) {
699- if(!strcmp(def->name, timecode_name))
700- break;
701+ while (def->name) {
702+ if (!strcmp(def->name, name))
703+ return def;
704 def++;
705 }
706-
707- if(!def->name) {
708- fprintf(stderr, "Timecode definition '%s' is not known.\n",
709- timecode_name);
710- return -1;
711- }
712-
713- //Copy the lookup table stuff
714- if (timecoder->tc_table == NULL) {
715- timecoder->tc_table = malloc(sizeof(struct timecode_def_t));
716- }
717- memcpy(timecoder->tc_table, def, sizeof(struct timecode_def_t));
718-
719- fprintf(stderr, "Allocating %d slots (%zuKb) for %d bit timecode (%s)\n",
720- 2 << timecoder->tc_table->bits, (2 << timecoder->tc_table->bits) * sizeof(unsigned int) / 1024,
721- timecoder->tc_table->bits, timecoder->tc_table->desc);
722-
723- timecoder->tc_table->lookup = malloc((2 << timecoder->tc_table->bits) * sizeof(unsigned int));
724- if(!timecoder->tc_table->lookup) {
725- perror("malloc");
726+ return NULL;
727+}
728+
729+
730+/* Where necessary, build the lookup table required for this timecode */
731+
732+static int build_lookup(struct timecode_def_t *def)
733+{
734+ unsigned int n;
735+ bits_t current, last;
736+
737+ if (def->lookup)
738 return 0;
739- }
740-
741- for(n = 0; n < ((unsigned int)2 << timecoder->tc_table->bits); n++)
742- timecoder->tc_table->lookup[n] = -1;
743-
744- current = timecoder->tc_table->seed;
745-
746- for(n = 0; n < timecoder->tc_table->length; n++) {
747- if(timecoder->tc_table->lookup[current] != -1) {
748- fprintf(stderr, "Timecode has wrapped; finishing here.\n");
749- return -1;
750- }
751-
752- timecoder->tc_table->lookup[current] = n;
753- current = (current >> 1) + (lfsr(current, timecoder) << (timecoder->tc_table->bits - 1));
754- //printf("n=%d\n", n);
755- }
756+
757+ fprintf(stderr, "Building LUT for %d bit %dHz timecode (%s)\n",
758+ def->bits, def->resolution, def->desc);
759+
760+ if (lut_init(&def->lut, def->length) == -1)
761+ return -1;
762+
763+ current = def->seed;
764+
765+ for (n = 0; n < def->length; n++) {
766+ /* timecode must not wrap */
767+ assert(lut_lookup(&def->lut, current) == (unsigned)-1);
768+ lut_push(&def->lut, current);
769+ last = current;
770+ current = fwd(current, def);
771+ assert(rev(current, def) == last);
772+ }
773+
774+ def->lookup = true;
775
776 return 0;
777 }
778
779
780-/* Free the timecoder lookup table when it is no longer needed */
781-
782-void timecoder_free_lookup(struct timecoder_t* timecoder) {
783- if (timecoder->tc_table->lookup)
784- {
785- free(timecoder->tc_table->lookup);
786- timecoder->tc_table->lookup = NULL;
787+/* Free the timecoder lookup tables when they are no longer needed */
788+
789+void timecoder_free_lookup(void) {
790+ struct timecode_def_t *def;
791+
792+ def = &timecode_def[0];
793+ while (def->name) {
794+ if (def->lookup)
795+ lut_clear(&def->lut);
796+ def++;
797 }
798 }
799
800@@ -234,40 +247,43 @@
801 {
802 ch->positive = 0;
803 ch->zero = 0;
804- ch->crossing_ticker = 0;
805 }
806
807
808-/* Initialise a timecode decoder */
809+/* Initialise a timecode decoder at the given reference speed */
810
811-void timecoder_init(struct timecoder_t *tc)
812+int timecoder_init(struct timecoder_t *tc, const char *def_name, double speed,
813+ unsigned int sample_rate)
814 {
815- int c;
816+ /* A definition contains a lookup table which can be shared
817+ * across multiple timecoders */
818+
819+ tc->def = find_definition(def_name);
820+ if (tc->def == NULL) {
821+ fprintf(stderr, "Timecode definition '%s' is not known.\n", def_name);
822+ return -1;
823+ }
824+ if (build_lookup(tc->def) == -1)
825+ return -1;
826+ tc->speed = speed;
827+
828+ tc->dt = 1.0 / sample_rate;
829+ tc->zero_alpha = tc->dt / (ZERO_RC + tc->dt);
830
831 tc->forwards = 1;
832- tc->rate = TIMECODER_RATE;
833-
834- tc->half_peak = 0;
835- tc->wave_peak = 0;
836- tc->ref_level = -1;
837- tc->signal_level = 0;
838-
839- init_channel(&tc->mono);
840- for(c = 0; c < TIMECODER_CHANNELS; c++)
841- init_channel(&tc->channel[c]);
842-
843- tc->crossings = 0;
844- tc->pitch_ticker = 0;
845-
846+ init_channel(&tc->primary);
847+ init_channel(&tc->secondary);
848+ pitch_init(&tc->pitch, tc->dt);
849+
850+ tc->ref_level = 32768.0;
851 tc->bitstream = 0;
852 tc->timecode = 0;
853 tc->valid_counter = 0;
854 tc->timecode_ticker = 0;
855
856 tc->mon = NULL;
857- tc->log_fd = -1;
858-
859- tc->tc_table = NULL;
860+
861+ return 0;
862 }
863
864
865@@ -283,13 +299,17 @@
866 * display of the incoming audio. Initialise one for the given
867 * timecoder */
868
869-void timecoder_monitor_init(struct timecoder_t *tc, int size, int scale)
870+int timecoder_monitor_init(struct timecoder_t *tc, int size)
871 {
872 tc->mon_size = size;
873- tc->mon_scale = scale;
874 tc->mon = malloc(SQ(tc->mon_size));
875+ if (tc->mon == NULL) {
876+ perror("malloc");
877+ return -1;
878+ }
879 memset(tc->mon, 0, SQ(tc->mon_size));
880 tc->mon_counter = 0;
881+ return 0;
882 }
883
884
885@@ -297,300 +317,212 @@
886
887 void timecoder_monitor_clear(struct timecoder_t *tc)
888 {
889- if(tc->mon) {
890+ if (tc->mon) {
891 free(tc->mon);
892 tc->mon = NULL;
893 }
894 }
895
896
897-static int detect_zero_crossing(struct timecoder_channel_t *ch,
898- signed short v, int rate)
899+static void detect_zero_crossing(struct timecoder_channel_t *ch,
900+ signed int v, float alpha)
901 {
902- int swapped;
903-
904 ch->crossing_ticker++;
905
906- swapped = 0;
907- if(v >= ch->zero + ZERO_THRESHOLD && !ch->positive) {
908- swapped = 1;
909+ ch->swapped = 0;
910+ if (v > ch->zero + ZERO_THRESHOLD && !ch->positive) {
911+ ch->swapped = 1;
912 ch->positive = 1;
913 ch->crossing_ticker = 0;
914- } else if(v < ch->zero - ZERO_THRESHOLD && ch->positive) {
915- swapped = 1;
916+ } else if (v < ch->zero - ZERO_THRESHOLD && ch->positive) {
917+ ch->swapped = 1;
918 ch->positive = 0;
919 ch->crossing_ticker = 0;
920 }
921
922- ch->zero += (v - ch->zero) * ZERO_AVG / rate;
923+ ch->zero += alpha * (v - ch->zero);
924+}
925+
926+
927+/* Plot the given sample value in the monitor (scope) */
928+
929+static void update_monitor(struct timecoder_t *tc, signed int x, signed int y)
930+{
931+ int px, py, p;
932+ float v, w;
933+
934+ if (!tc->mon)
935+ return;
936+
937+ /* Decay the pixels already in the montior */
938+
939+ if (++tc->mon_counter % MONITOR_DECAY_EVERY == 0) {
940+ for (p = 0; p < SQ(tc->mon_size); p++) {
941+ if (tc->mon[p])
942+ tc->mon[p] = tc->mon[p] * 7 / 8;
943+ }
944+ }
945+
946+ v = (float)x / tc->ref_level / 2;
947+ w = (float)y / tc->ref_level / 2;
948+
949+ px = tc->mon_size / 2 + (v * tc->mon_size / 2);
950+ py = tc->mon_size / 2 + (w * tc->mon_size / 2);
951+
952+ /* Set the pixel value to white */
953+
954+ if (px > 0 && px < tc->mon_size && py > 0 && py < tc->mon_size)
955+ tc->mon[py * tc->mon_size + px] = 0xff;
956+}
957+
958+
959+/* Process a single bitstream reading */
960+
961+static void process_bitstream(struct timecoder_t *tc, signed int m)
962+{
963+ bits_t b;
964+
965+ b = m > tc->ref_level;
966+
967+ /* Add it to the bitstream, and work out what we were expecting
968+ * (timecode). */
969+
970+ /* tc->bitstream is always in the order it is physically placed on
971+ * the vinyl, regardless of the direction. */
972+
973+ if (tc->forwards) {
974+ tc->timecode = fwd(tc->timecode, tc->def);
975+ tc->bitstream = (tc->bitstream >> 1)
976+ + (b << (tc->def->bits - 1));
977+
978+ } else {
979+ bits_t mask;
980+
981+ mask = ((1 << tc->def->bits) - 1);
982+ tc->timecode = rev(tc->timecode, tc->def);
983+ tc->bitstream = ((tc->bitstream << 1) & mask) + b;
984+ }
985+
986+ if (tc->timecode == tc->bitstream)
987+ tc->valid_counter++;
988+ else {
989+ tc->timecode = tc->bitstream;
990+ tc->valid_counter = 0;
991+ }
992+
993+ /* Take note of the last time we read a valid timecode */
994
995- return swapped;
996+ tc->timecode_ticker = 0;
997+
998+ /* Adjust the reference level based on this new peak */
999+
1000+ tc->ref_level = (tc->ref_level * (REF_PEAKS_AVG - 1) + m) / REF_PEAKS_AVG;
1001+
1002+#ifdef DEBUG_BITSTREAM
1003+ fprintf(stderr, "%+6d zero, %+6d (ref %+6d)\t= %d%c (%5d)\n",
1004+ tc->primary.zero,
1005+ m,
1006+ tc->ref_level,
1007+ b,
1008+ tc->valid_counter == 0 ? 'x' : ' ',
1009+ tc->valid_counter);
1010+#endif
1011+}
1012+
1013+
1014+/* Process a single sample from the incoming audio */
1015+
1016+static void process_sample(struct timecoder_t *tc,
1017+ signed int primary, signed int secondary)
1018+{
1019+ signed int m; /* pcm sample, sum of two shorts */
1020+
1021+ detect_zero_crossing(&tc->primary, primary, tc->zero_alpha);
1022+ detect_zero_crossing(&tc->secondary, secondary, tc->zero_alpha);
1023+
1024+ m = abs(primary - tc->primary.zero);
1025+
1026+ /* If an axis has been crossed, use the direction of the crossing
1027+ * to work out the direction of the vinyl */
1028+
1029+ if (tc->primary.swapped) {
1030+ tc->forwards = (tc->primary.positive != tc->secondary.positive);
1031+ if (tc->def->flags & SWITCH_PHASE)
1032+ tc->forwards = !tc->forwards;
1033+ } if (tc->secondary.swapped) {
1034+ tc->forwards = (tc->primary.positive == tc->secondary.positive);
1035+ if (tc->def->flags & SWITCH_PHASE)
1036+ tc->forwards = !tc->forwards;
1037+ }
1038+
1039+ /* If any axis has been crossed, register movement using the pitch
1040+ * counters */
1041+
1042+ if (!tc->primary.swapped && !tc->secondary.swapped)
1043+ pitch_dt_observation(&tc->pitch, 0.0);
1044+ else {
1045+ float dx;
1046+
1047+ dx = 1.0 / tc->def->resolution / 4;
1048+ if (!tc->forwards)
1049+ dx = -dx;
1050+ pitch_dt_observation(&tc->pitch, dx);
1051+ }
1052+
1053+ /* If we have crossed the primary channel in the right polarity,
1054+ * it's time to read off a timecode 0 or 1 value */
1055+
1056+ if (tc->secondary.swapped &&
1057+ tc->primary.positive == ((tc->def->flags & SWITCH_POLARITY) == 0))
1058+ {
1059+ process_bitstream(tc, m);
1060+ }
1061+
1062+ tc->timecode_ticker++;
1063 }
1064
1065
1066 /* Submit and decode a block of PCM audio data to the timecoder */
1067
1068-int timecoder_submit(struct timecoder_t *tc, const signed short *pcm, int samples)
1069+void timecoder_submit(struct timecoder_t *tc, const signed short *pcm, size_t npcm)
1070 {
1071- int b, l, /* bitstream and timecode bits */
1072- s, c,
1073- x, y, p, /* monitor coordinates */
1074- v,
1075- offset,
1076- swapped,
1077- monitor_centre;
1078- signed short w; /* pcm sample values */
1079- unsigned int mask;
1080-
1081- b = 0;
1082- l = 0;
1083-
1084- mask = ((1 << tc->tc_table->bits) - 1);
1085- monitor_centre = tc->mon_size / 2;
1086-
1087- offset = 0;
1088-
1089- for(s = 0; s < samples; s++) {
1090-
1091- for(c = 0; c < TIMECODER_CHANNELS; c++)
1092- detect_zero_crossing(&tc->channel[c], pcm[offset + c], tc->rate);
1093-
1094- /* Read from the mono channel */
1095-
1096- v = pcm[offset] + pcm[offset + 1];
1097- swapped = detect_zero_crossing(&tc->mono, v, tc->rate);
1098-
1099- /* If a sign change in the (zero corrected) audio has
1100- * happened, log the peak information */
1101-
1102- if(swapped) {
1103-
1104- /* Work out whether half way through a cycle we are
1105- * looking for the wave to be positive or negative */
1106-
1107- if(tc->mono.positive == (tc->tc_table->polarity ^ tc->forwards)) {
1108-
1109- /* Entering the second half of a wave cycle */
1110-
1111- tc->half_peak = tc->wave_peak;
1112-
1113- } else {
1114-
1115- /* Completed a full wave cycle, so time to analyse the
1116- * level and work out whether it's a 1 or 0 */
1117-
1118- b = tc->wave_peak + tc->half_peak > tc->ref_level;
1119-
1120- /* Log binary timecode */
1121-
1122- if(tc->log_fd != -1)
1123- write(tc->log_fd, b ? "1" : "0", 1);
1124-
1125- /* Add it to the bitstream, and work out what we were
1126- * expecting (timecode). */
1127-
1128- /* tc->bitstream is always in the order it is
1129- * physically placed on the vinyl, regardless of the
1130- * direction. */
1131-
1132- if(tc->forwards) {
1133- l = lfsr(tc->timecode, tc);
1134-
1135- tc->bitstream = (tc->bitstream >> 1)
1136- + (b << (tc->tc_table->bits - 1));
1137-
1138- tc->timecode = (tc->timecode >> 1)
1139- + (l << (tc->tc_table->bits - 1));
1140-
1141- } else {
1142- l = lfsr_rev(tc->timecode, tc);
1143-
1144- tc->bitstream = ((tc->bitstream << 1) & mask) + b;
1145- tc->timecode = ((tc->timecode << 1) & mask) + l;
1146- }
1147-
1148- if(b == l) {
1149- tc->valid_counter++;
1150- } else {
1151- tc->timecode = tc->bitstream;
1152- tc->valid_counter = 0;
1153- }
1154-
1155- /* Take note of the last time we read a valid timecode */
1156-
1157- tc->timecode_ticker = 0;
1158-
1159- /* Adjust the reference level based on the peaks seen
1160- * in this cycle */
1161-
1162- if(tc->ref_level == -1)
1163- tc->ref_level = tc->half_peak + tc->wave_peak;
1164- else {
1165- tc->ref_level = (tc->ref_level * (REF_PEAKS_AVG - 1)
1166- + tc->half_peak + tc->wave_peak)
1167- / REF_PEAKS_AVG;
1168- }
1169-
1170- }
1171-
1172- /* Calculate the immediate direction from phase difference,
1173- * based on the last channel to cross zero */
1174-
1175- if(tc->channel[0].crossing_ticker > tc->channel[1].crossing_ticker)
1176- tc->forwards = 1;
1177- else
1178- tc->forwards = 0;
1179-
1180- if(tc->forwards)
1181- tc->crossings++;
1182- else
1183- tc->crossings--;
1184-
1185- tc->pitch_ticker += tc->crossing_ticker;
1186- tc->crossing_ticker = 0;
1187- tc->wave_peak = 0;
1188-
1189- } /* swapped */
1190-
1191- tc->crossing_ticker++;
1192- tc->timecode_ticker++;
1193-
1194- /* Find the zero-normalised sample of the peak value from
1195- * the input */
1196-
1197- w = abs(v - tc->mono.zero);
1198- if(w > tc->wave_peak)
1199- tc->wave_peak = w;
1200-
1201- /* Take a rolling average of zero and signal level */
1202-
1203- tc->signal_level += (w - tc->signal_level) * SIGNAL_AVG / tc->rate;
1204-
1205- /* Update the monitor to add the incoming sample */
1206-
1207- if(tc->mon) {
1208-
1209- /* Decay the pixels already in the montior */
1210-
1211- if(++tc->mon_counter % MONITOR_DECAY_EVERY == 0) {
1212- for(p = 0; p < SQ(tc->mon_size); p++) {
1213- if(tc->mon[p])
1214- tc->mon[p] = tc->mon[p] * 7 / 8;
1215- }
1216- }
1217-
1218- v = pcm[offset]; /* first channel */
1219- w = pcm[offset + 1]; /* second channel */
1220-
1221- x = monitor_centre + (v * tc->mon_size * tc->mon_scale / 32768);
1222- y = monitor_centre + (w * tc->mon_size * tc->mon_scale / 32768);
1223-
1224- /* Set the pixel value to white */
1225-
1226- if(x > 0 && x < tc->mon_size && y > 0 && y < tc->mon_size)
1227- tc->mon[y * tc->mon_size + x] = 0xff;
1228+ while (npcm--) {
1229+ signed int primary, secondary;
1230+
1231+ if (tc->def->flags & SWITCH_PRIMARY) {
1232+ primary = pcm[0];
1233+ secondary = pcm[1];
1234+ } else {
1235+ primary = pcm[1];
1236+ secondary = pcm[0];
1237 }
1238-
1239- offset += TIMECODER_CHANNELS;
1240-
1241- } /* for each sample */
1242-
1243- /* Print debugging information */
1244-
1245-#if 0
1246- fprintf(stderr, "%+6d +/%4d -/%4d (%4d,%4d)\t= %d (%d) %c %d"
1247- "\t[crossings: %d %d]",
1248- tc->mono.zero,
1249- tc->half_peak,
1250- tc->wave_peak,
1251- tc->ref_level >> 1,
1252- tc->signal_level,
1253- b, l, b == l ? ' ' : 'x',
1254- tc->valid_counter,
1255- tc->crossings,
1256- tc->pitch_ticker);
1257-
1258- if(tc->pitch_ticker)
1259- fprintf(stderr, " = %d", tc->rate * tc->crossings / tc->pitch_ticker);
1260-
1261- fputc('\n', stderr);
1262-#endif
1263-
1264- return 0;
1265-}
1266-
1267-
1268-/* Return the timecode pitch, based on cycles of the sine wave. This
1269- * function can only be called by one context, at it resets the state
1270- * of the counter in the timecoder. */
1271-
1272-int timecoder_get_pitch(struct timecoder_t *tc, float *pitch)
1273-{
1274- /* Let the caller know if there's no data to gather pitch from */
1275-
1276- if(tc->crossings == 0)
1277- return -1;
1278-
1279- /* Value of tc->crossings may be negative in reverse */
1280-
1281- *pitch = tc->rate * (float)tc->crossings / tc->pitch_ticker
1282- / (tc->tc_table->resolution * 2);
1283-
1284- tc->crossings = 0;
1285- tc->pitch_ticker = 0;
1286-
1287- return 0;
1288+
1289+ process_sample(tc, primary, secondary);
1290+
1291+ update_monitor(tc, pcm[0], pcm[1]);
1292+ pcm += TIMECODER_CHANNELS;
1293+ }
1294 }
1295
1296
1297 /* Return the known position in the timecode, or -1 if not known. If
1298 * two few bits have been error-checked, then this also counts as
1299- * invalid. If 'when' is given, return the time, in input samples since
1300- * this value was read. */
1301+ * invalid. If 'when' is given, return the time, in seconds since this
1302+ * value was read. */
1303
1304-signed int timecoder_get_position(struct timecoder_t *tc, int *when)
1305+signed int timecoder_get_position(struct timecoder_t *tc, float *when)
1306 {
1307 signed int r;
1308
1309- if(tc->valid_counter > VALID_BITS) {
1310- r = tc->tc_table->lookup[tc->bitstream];
1311+ if (tc->valid_counter > VALID_BITS) {
1312+ r = lut_lookup(&tc->def->lut, tc->bitstream) / tc->speed;
1313
1314- if(r >= 0) {
1315- if(when)
1316- *when = tc->timecode_ticker;
1317+ if (r >= 0) {
1318+ if (when)
1319+ *when = tc->timecode_ticker * tc->dt;
1320 return r;
1321 }
1322 }
1323
1324 return -1;
1325 }
1326-
1327-
1328-/* Return non-zero if there is any timecode signal available */
1329-
1330-int timecoder_get_alive(struct timecoder_t *tc)
1331-{
1332- if(tc->signal_level < SIGNAL_THRESHOLD)
1333- return 0;
1334-
1335- return 1;
1336-}
1337-
1338-
1339-/* Return the last 'safe' timecode value on the record. Beyond this
1340- * value, we probably want to ignore the timecode values, as we will
1341- * hit the label of the record. */
1342-
1343-unsigned int timecoder_get_safe(struct timecoder_t *tc)
1344-{
1345- return tc->tc_table->safe;
1346-}
1347-
1348-
1349-/* Return the resolution of the timecode. This is the number of bits
1350- * per second, which corresponds to the frequency of the sine wave */
1351-
1352-int timecoder_get_resolution(struct timecoder_t *tc)
1353-{
1354- return tc->tc_table->resolution;
1355-}
1356
1357=== modified file 'mixxx/lib/xwax/timecoder.h'
1358--- mixxx/lib/xwax/timecoder.h 2011-04-08 06:04:27 +0000
1359+++ mixxx/lib/xwax/timecoder.h 2011-04-16 23:24:33 +0000
1360@@ -1,5 +1,5 @@
1361 /*
1362- * Copyright (C) 2008 Mark Hills <mark@pogo.org.uk>
1363+ * Copyright (C) 2010 Mark Hills <mark@pogo.org.uk>
1364 *
1365 * This program is free software; you can redistribute it and/or
1366 * modify it under the terms of the GNU General Public License
1367@@ -20,80 +20,118 @@
1368 #ifndef TIMECODER_H
1369 #define TIMECODER_H
1370
1371+#ifndef _MSC_VER
1372+#include <stdbool.h>
1373+#endif
1374+
1375+/* #include "device.h" */
1376+#include "lut.h"
1377+#include "pitch.h"
1378+
1379 #define TIMECODER_CHANNELS 2
1380-#define TIMECODER_RATE 44100 //Default rate - Albert
1381-
1382-#define MAX_BITS 32 /* bits in an int */
1383+
1384+
1385+typedef unsigned int bits_t;
1386+
1387
1388 struct timecode_def_t {
1389 char *name, *desc;
1390 int bits, /* number of bits in string */
1391 resolution, /* wave cycles per second */
1392- tap[MAX_BITS], ntaps, /* LFSR taps */
1393- polarity; /* cycle begins POLARITY_POSITIVE or POLARITY_NEGATIVE */
1394- unsigned int seed, /* LFSR value at timecode zero */
1395- length, /* in cycles */
1396+ flags;
1397+ bits_t seed, /* LFSR value at timecode zero */
1398+ taps; /* central LFSR taps, excluding end taps */
1399+ unsigned int length, /* in cycles */
1400 safe; /* last 'safe' timecode number (for auto disconnect) */
1401- signed int *lookup; /* pointer to built lookup table */
1402+ bool lookup; /* true if lut has been generated */
1403+ struct lut_t lut;
1404 };
1405
1406+
1407 struct timecoder_channel_t {
1408- int positive; /* wave is in positive part of cycle */
1409+ int positive, /* wave is in positive part of cycle */
1410+ swapped; /* wave recently swapped polarity */
1411 signed int zero;
1412- int crossing_ticker; /* samples since we last crossed zero */
1413+ unsigned int crossing_ticker; /* samples since we last crossed zero */
1414 };
1415
1416
1417 struct timecoder_t {
1418- int forwards, rate;
1419-
1420- /* Signal levels */
1421-
1422- signed int signal_level, half_peak, wave_peak, ref_level;
1423- struct timecoder_channel_t mono, channel[TIMECODER_CHANNELS];
1424+ struct timecode_def_t *def;
1425+ double speed;
1426+
1427+ /* Precomputed values */
1428+
1429+ float dt, zero_alpha;
1430
1431 /* Pitch information */
1432
1433- int crossings, /* number of zero crossings */
1434- pitch_ticker, /* number of samples from which crossings counted */
1435- crossing_ticker; /* stored for incrementing pitch_ticker */
1436+ int forwards;
1437+ struct timecoder_channel_t primary, secondary;
1438+ struct pitch_t pitch;
1439
1440 /* Numerical timecode */
1441
1442- unsigned int bitstream, /* actual bits from the record */
1443+ signed int ref_level;
1444+ bits_t bitstream, /* actual bits from the record */
1445 timecode; /* corrected timecode */
1446- int valid_counter, /* number of successful error checks */
1447+ unsigned int valid_counter, /* number of successful error checks */
1448 timecode_ticker; /* samples since valid timecode was read */
1449
1450 /* Feedback */
1451
1452 unsigned char *mon; /* x-y array */
1453- int mon_size, mon_counter, mon_scale,
1454- log_fd; /* optional file descriptor to log to, or -1 for none */
1455-
1456- struct timecode_def_t *tc_table;
1457+ int mon_size, mon_counter;
1458 };
1459
1460
1461-/* Building the lookup table is global. Need a good way to share
1462- * lookup tables soon, so we can use a different timecode on
1463- * each timecoder, and switch between them. */
1464-
1465-int timecoder_build_lookup(char *timecode_name, struct timecoder_t *timecoder);
1466-void timecoder_free_lookup(struct timecoder_t *timecoder);
1467-
1468-void timecoder_init(struct timecoder_t *tc);
1469+void timecoder_free_lookup(void);
1470+
1471+int timecoder_init(struct timecoder_t *tc, const char *def_name, double speed,
1472+ unsigned int sample_rate);
1473 void timecoder_clear(struct timecoder_t *tc);
1474
1475-void timecoder_monitor_init(struct timecoder_t *tc, int size, int scale);
1476+int timecoder_monitor_init(struct timecoder_t *tc, int size);
1477 void timecoder_monitor_clear(struct timecoder_t *tc);
1478
1479-int timecoder_submit(struct timecoder_t *tc, const signed short *aud, int samples);
1480-
1481-int timecoder_get_pitch(struct timecoder_t *tc, float *pitch);
1482-signed int timecoder_get_position(struct timecoder_t *tc, int *when);
1483-int timecoder_get_alive(struct timecoder_t *tc);
1484-unsigned int timecoder_get_safe(struct timecoder_t *tc);
1485-int timecoder_get_resolution(struct timecoder_t *tc);
1486+void timecoder_submit(struct timecoder_t *tc, const signed short *pcm, size_t npcm);
1487+
1488+signed int timecoder_get_position(struct timecoder_t *tc, float *when);
1489+
1490+
1491+/* Return the pitch relative to reference playback speed */
1492+
1493+static inline float timecoder_get_pitch(struct timecoder_t *tc)
1494+{
1495+ return pitch_current(&tc->pitch) / tc->speed;
1496+}
1497+
1498+
1499+/* The last 'safe' timecode value on the record. Beyond this value, we
1500+ * probably want to ignore the timecode values, as we will hit the
1501+ * label of the record. */
1502+
1503+static inline unsigned int timecoder_get_safe(struct timecoder_t *tc)
1504+{
1505+ return tc->def->safe;
1506+}
1507+
1508+
1509+/* The resolution of the timecode. This is the number of bits per
1510+ * second at reference playback speed */
1511+
1512+static inline double timecoder_get_resolution(struct timecoder_t *tc)
1513+{
1514+ return tc->def->resolution * tc->speed;
1515+}
1516+
1517+
1518+/* The number of revolutions per second of the timecode vinyl,
1519+ * used only for visual display */
1520+
1521+static inline double timecoder_revs_per_sec(struct timecoder_t *tc)
1522+{
1523+ return (33.0 + 1.0 / 3) * tc->speed / 60;
1524+}
1525
1526 #endif
1527
1528=== renamed file 'mixxx/lib/xwax/timecoder_win32.c' => 'mixxx/lib/xwax/timecoder_win32.cpp'
1529--- mixxx/lib/xwax/timecoder_win32.c 2009-08-06 06:36:18 +0000
1530+++ mixxx/lib/xwax/timecoder_win32.cpp 2011-04-16 23:24:33 +0000
1531@@ -1,5 +1,5 @@
1532 /*
1533- * Copyright (C) 2008 Mark Hills <mark@pogo.org.uk>
1534+ * Copyright (C) 2010 Mark Hills <mark@pogo.org.uk>
1535 *
1536 * This program is free software; you can redistribute it and/or
1537 * modify it under the terms of the GNU General Public License
1538@@ -17,18 +17,19 @@
1539 *
1540 */
1541
1542+#include <assert.h>
1543 #include <stdio.h>
1544 #include <stdlib.h>
1545 #include <string.h>
1546-//#include <unistd.h>
1547+#ifndef _MSC_VER
1548+#include <unistd.h>
1549+#endif
1550
1551 #include "timecoder.h"
1552
1553 #define ZERO_THRESHOLD 128
1554-#define SIGNAL_THRESHOLD 256
1555
1556-#define ZERO_AVG 1024
1557-#define SIGNAL_AVG 256
1558+#define ZERO_RC 0.001 /* time constant for zero/rumble filter */
1559
1560 #define REF_PEAKS_AVG 48 /* in wave cycles */
1561
1562@@ -45,75 +46,95 @@
1563
1564 /* Timecode definitions */
1565
1566-
1567-#define POLARITY_NEGATIVE 0
1568-#define POLARITY_POSITIVE 1
1569-
1570-struct timecode_def_t timecode_def[] = {
1571+#define SWITCH_PHASE 0x1 /* tone phase difference of 270 (not 90) degrees */
1572+#define SWITCH_PRIMARY 0x2 /* use left channel (not right) as primary */
1573+#define SWITCH_POLARITY 0x4 /* read bit values in negative (not positive) */
1574+
1575+
1576+static struct timecode_def_t timecode_def[] = {
1577 {
1578 "serato_2a",
1579 "Serato 2nd Ed., side A",
1580 20,
1581- 1000,
1582- {2, 5, 6, 7, 8, 13, 14, 16, 17},
1583- 9,
1584- POLARITY_POSITIVE,
1585+ 1000,
1586+ 0,
1587 0x59017,
1588+ 0x361e4,
1589 712000,
1590- 707000,
1591- NULL
1592+ 625000,
1593+ false
1594 },
1595 {
1596 "serato_2b",
1597 "Serato 2nd Ed., side B",
1598 20,
1599 1000,
1600- {3, 4, 6, 7, 12, 13, 14, 15, 18}, /* reverse of side A */
1601- 9,
1602- POLARITY_POSITIVE,
1603+ 0,
1604 0x8f3c6,
1605+ 0x4f0d8, /* reverse of side A */
1606 922000,
1607- 917000,
1608- NULL
1609+ 905000,
1610+ false
1611 },
1612 {
1613 "serato_cd",
1614 "Serato CD",
1615 20,
1616 1000,
1617- {2, 4, 6, 8, 10, 11, 14, 16, 17},
1618- 9,
1619- POLARITY_POSITIVE,
1620+ 0,
1621 0xd8b40,
1622- 910000,
1623- 900000,
1624- NULL
1625+ 0x34d54,
1626+ 950000,
1627+ 940000,
1628+ false
1629 },
1630 {
1631 "traktor_a",
1632 "Traktor Scratch, side A",
1633 23,
1634 2000,
1635- {6, 12, 18},
1636- 3,
1637- POLARITY_NEGATIVE,
1638+ SWITCH_PRIMARY | SWITCH_POLARITY | SWITCH_PHASE,
1639 0x134503,
1640+ 0x041040,
1641 1500000,
1642- 1480000,
1643- NULL
1644+ 1463000,
1645+ false
1646 },
1647 {
1648 "traktor_b",
1649 "Traktor Scratch, side B",
1650 23,
1651- 2000,
1652- {6, 12, 18},
1653- 3,
1654- POLARITY_NEGATIVE,
1655+ 2000,
1656+ SWITCH_PRIMARY | SWITCH_POLARITY | SWITCH_PHASE,
1657 0x32066c,
1658+ 0x041040, /* same as side A */
1659 2110000,
1660- 2090000,
1661- NULL
1662+ 2068000,
1663+ false
1664+ },
1665+ {
1666+ "mixvibes_v2",
1667+ "MixVibes V2",
1668+ 20,
1669+ 1300,
1670+ SWITCH_PHASE,
1671+ 0x22c90,
1672+ 0x00008,
1673+ 950000,
1674+ 923000,
1675+ false
1676+ },
1677+ {
1678+ "mixvibes_7inch",
1679+ "MixVibes 7\"",
1680+ 20,
1681+ 1300,
1682+ SWITCH_PHASE,
1683+ 0x22c90,
1684+ 0x00008,
1685+ 312000,
1686+ 310000,
1687+ false
1688 },
1689 {
1690 NULL
1691@@ -121,110 +142,105 @@
1692 };
1693
1694
1695-//struct timecode_def_t *def;
1696-
1697-
1698 /* Linear Feeback Shift Register in the forward direction. New values
1699 * are generated at the least-significant bit. */
1700
1701-static int lfsr(unsigned int code, struct timecoder_t *timecoder)
1702-{
1703- unsigned int r;
1704- char s, n;
1705-
1706- r = code & 1;
1707-
1708- for(n = 0; n < timecoder->tc_table->ntaps; n++) {
1709- s = *(timecoder->tc_table->tap + n);
1710- r += (code & (1 << s)) >> s;
1711- }
1712-
1713- return r & 0x1;
1714-}
1715-
1716-
1717-/* Linear Feeback Shift Register in the reverse direction. New values
1718- * are generated at the most-significant bit. */
1719-
1720-static /* inline */ int lfsr_rev(unsigned int code, struct timecoder_t *timecoder) // inline causes compile failure on MSVC++ 2005 EE
1721-{
1722- unsigned int r;
1723- char s, n;
1724-
1725- r = (code & (1 << (timecoder->tc_table->bits - 1))) >> (timecoder->tc_table->bits - 1);
1726-
1727- for(n = 0; n < timecoder->tc_table->ntaps; n++) {
1728- s = *(timecoder->tc_table->tap + n) - 1;
1729- r += (code & (1 << s)) >> s;
1730- }
1731-
1732- return r & 0x1;
1733-}
1734-
1735-
1736-/* Setup globally, for a chosen timecode definition */
1737-
1738-int timecoder_build_lookup(char *timecode_name, struct timecoder_t *timecoder) {
1739- unsigned int n, current;
1740-
1741+static inline bits_t lfsr(bits_t code, bits_t taps)
1742+{
1743+ bits_t taken;
1744+ int xrs;
1745+
1746+ taken = code & taps;
1747+ xrs = 0;
1748+ while (taken != 0x0) {
1749+ xrs += taken & 0x1;
1750+ taken >>= 1;
1751+ }
1752+
1753+ return xrs & 0x1;
1754+}
1755+
1756+
1757+static inline bits_t fwd(bits_t current, struct timecode_def_t *def)
1758+{
1759+ bits_t l;
1760+
1761+ /* New bits are added at the MSB; shift right by one */
1762+
1763+ l = lfsr(current, def->taps | 0x1);
1764+ return (current >> 1) | (l << (def->bits - 1));
1765+}
1766+
1767+
1768+static inline bits_t rev(bits_t current, struct timecode_def_t *def)
1769+{
1770+ bits_t l, mask;
1771+
1772+ /* New bits are added at the LSB; shift left one and mask */
1773+
1774+ mask = (1 << def->bits) - 1;
1775+ l = lfsr(current, (def->taps >> 1) | (0x1 << (def->bits - 1)));
1776+ return ((current << 1) & mask) | l;
1777+}
1778+
1779+
1780+static struct timecode_def_t* find_definition(const char *name)
1781+{
1782 struct timecode_def_t *def;
1783+
1784 def = &timecode_def[0];
1785-
1786- while(def->name) {
1787- if(!strcmp(def->name, timecode_name))
1788- break;
1789+ while (def->name) {
1790+ if (!strcmp(def->name, name))
1791+ return def;
1792 def++;
1793 }
1794-
1795- if(!def->name) {
1796- fprintf(stderr, "Timecode definition '%s' is not known.\n",
1797- timecode_name);
1798- return -1;
1799- }
1800-
1801- //Copy the lookup table stuff
1802- if (timecoder->tc_table == NULL) {
1803- timecoder->tc_table = malloc(sizeof(struct timecode_def_t));
1804- }
1805- memcpy(timecoder->tc_table, def, sizeof(struct timecode_def_t));
1806-
1807- //fprintf(stderr, "Allocating %d slots (%zuKb) for %d bit timecode (%s)\n",
1808- // 2 << timecoder->tc_table->bits, (2 << timecoder->tc_table->bits) * sizeof(unsigned int) / 1024,
1809- // timecoder->tc_table->bits, timecoder->tc_table->desc);
1810-
1811- timecoder->tc_table->lookup = malloc((2 << timecoder->tc_table->bits) * sizeof(unsigned int));
1812- if(!timecoder->tc_table->lookup) {
1813- perror("malloc");
1814+ return NULL;
1815+}
1816+
1817+
1818+/* Where necessary, build the lookup table required for this timecode */
1819+
1820+static int build_lookup(struct timecode_def_t *def)
1821+{
1822+ unsigned int n;
1823+ bits_t current, last;
1824+
1825+ if (def->lookup)
1826 return 0;
1827- }
1828-
1829- for(n = 0; n < ((unsigned int)2 << timecoder->tc_table->bits); n++)
1830- timecoder->tc_table->lookup[n] = -1;
1831-
1832- current = timecoder->tc_table->seed;
1833-
1834- for(n = 0; n < timecoder->tc_table->length; n++) {
1835- if(timecoder->tc_table->lookup[current] != -1) {
1836- //fprintf(stderr, "Timecode has wrapped; finishing here.\n");
1837- return -1;
1838- }
1839-
1840- timecoder->tc_table->lookup[current] = n;
1841- current = (current >> 1) + (lfsr(current, timecoder) << (timecoder->tc_table->bits - 1));
1842- //printf("n=%d\n", n);
1843- }
1844+
1845+ fprintf(stderr, "Building LUT for %d bit %dHz timecode (%s)\n",
1846+ def->bits, def->resolution, def->desc);
1847+
1848+ if (lut_init(&def->lut, def->length) == -1)
1849+ return -1;
1850+
1851+ current = def->seed;
1852+
1853+ for (n = 0; n < def->length; n++) {
1854+ /* timecode must not wrap */
1855+ assert(lut_lookup(&def->lut, current) == (unsigned)-1);
1856+ lut_push(&def->lut, current);
1857+ last = current;
1858+ current = fwd(current, def);
1859+ assert(rev(current, def) == last);
1860+ }
1861+
1862+ def->lookup = true;
1863
1864 return 0;
1865 }
1866
1867
1868-/* Free the timecoder lookup table when it is no longer needed */
1869-
1870-void timecoder_free_lookup(struct timecoder_t* timecoder) {
1871- if (timecoder->tc_table->lookup)
1872- {
1873- free(timecoder->tc_table->lookup);
1874- timecoder->tc_table->lookup = NULL;
1875+/* Free the timecoder lookup tables when they are no longer needed */
1876+
1877+void timecoder_free_lookup(void) {
1878+ struct timecode_def_t *def;
1879+
1880+ def = &timecode_def[0];
1881+ while (def->name) {
1882+ if (def->lookup)
1883+ lut_clear(&def->lut);
1884+ def++;
1885 }
1886 }
1887
1888@@ -233,40 +249,43 @@
1889 {
1890 ch->positive = 0;
1891 ch->zero = 0;
1892- ch->crossing_ticker = 0;
1893 }
1894
1895
1896-/* Initialise a timecode decoder */
1897+/* Initialise a timecode decoder at the given reference speed */
1898
1899-void timecoder_init(struct timecoder_t *tc)
1900+int timecoder_init(struct timecoder_t *tc, const char *def_name, double speed,
1901+ unsigned int sample_rate)
1902 {
1903- int c;
1904+ /* A definition contains a lookup table which can be shared
1905+ * across multiple timecoders */
1906+
1907+ tc->def = find_definition(def_name);
1908+ if (tc->def == NULL) {
1909+ fprintf(stderr, "Timecode definition '%s' is not known.\n", def_name);
1910+ return -1;
1911+ }
1912+ if (build_lookup(tc->def) == -1)
1913+ return -1;
1914+ tc->speed = speed;
1915+
1916+ tc->dt = 1.0 / sample_rate;
1917+ tc->zero_alpha = tc->dt / (ZERO_RC + tc->dt);
1918
1919 tc->forwards = 1;
1920- tc->rate = TIMECODER_RATE;
1921-
1922- tc->half_peak = 0;
1923- tc->wave_peak = 0;
1924- tc->ref_level = -1;
1925- tc->signal_level = 0;
1926-
1927- init_channel(&tc->mono);
1928- for(c = 0; c < TIMECODER_CHANNELS; c++)
1929- init_channel(&tc->channel[c]);
1930-
1931- tc->crossings = 0;
1932- tc->pitch_ticker = 0;
1933-
1934+ init_channel(&tc->primary);
1935+ init_channel(&tc->secondary);
1936+ pitch_init(&tc->pitch, tc->dt);
1937+
1938+ tc->ref_level = 32768.0;
1939 tc->bitstream = 0;
1940 tc->timecode = 0;
1941 tc->valid_counter = 0;
1942 tc->timecode_ticker = 0;
1943
1944 tc->mon = NULL;
1945- tc->log_fd = -1;
1946-
1947- tc->tc_table = NULL;
1948+
1949+ return 0;
1950 }
1951
1952
1953@@ -282,13 +301,17 @@
1954 * display of the incoming audio. Initialise one for the given
1955 * timecoder */
1956
1957-void timecoder_monitor_init(struct timecoder_t *tc, int size, int scale)
1958+int timecoder_monitor_init(struct timecoder_t *tc, int size)
1959 {
1960 tc->mon_size = size;
1961- tc->mon_scale = scale;
1962- tc->mon = malloc(SQ(tc->mon_size));
1963+ tc->mon = (unsigned char*)malloc(SQ(tc->mon_size));
1964+ if (tc->mon == NULL) {
1965+ perror("malloc");
1966+ return -1;
1967+ }
1968 memset(tc->mon, 0, SQ(tc->mon_size));
1969 tc->mon_counter = 0;
1970+ return 0;
1971 }
1972
1973
1974@@ -296,300 +319,212 @@
1975
1976 void timecoder_monitor_clear(struct timecoder_t *tc)
1977 {
1978- if(tc->mon) {
1979+ if (tc->mon) {
1980 free(tc->mon);
1981 tc->mon = NULL;
1982 }
1983 }
1984
1985
1986-static int detect_zero_crossing(struct timecoder_channel_t *ch,
1987- signed short v, int rate)
1988+static void detect_zero_crossing(struct timecoder_channel_t *ch,
1989+ signed int v, float alpha)
1990 {
1991- int swapped;
1992-
1993 ch->crossing_ticker++;
1994
1995- swapped = 0;
1996- if(v >= ch->zero + ZERO_THRESHOLD && !ch->positive) {
1997- swapped = 1;
1998+ ch->swapped = 0;
1999+ if (v > ch->zero + ZERO_THRESHOLD && !ch->positive) {
2000+ ch->swapped = 1;
2001 ch->positive = 1;
2002 ch->crossing_ticker = 0;
2003- } else if(v < ch->zero - ZERO_THRESHOLD && ch->positive) {
2004- swapped = 1;
2005+ } else if (v < ch->zero - ZERO_THRESHOLD && ch->positive) {
2006+ ch->swapped = 1;
2007 ch->positive = 0;
2008 ch->crossing_ticker = 0;
2009 }
2010
2011- ch->zero += (v - ch->zero) * ZERO_AVG / rate;
2012+ ch->zero += alpha * (v - ch->zero);
2013+}
2014+
2015+
2016+/* Plot the given sample value in the monitor (scope) */
2017+
2018+static void update_monitor(struct timecoder_t *tc, signed int x, signed int y)
2019+{
2020+ int px, py, p;
2021+ float v, w;
2022+
2023+ if (!tc->mon)
2024+ return;
2025+
2026+ /* Decay the pixels already in the montior */
2027+
2028+ if (++tc->mon_counter % MONITOR_DECAY_EVERY == 0) {
2029+ for (p = 0; p < SQ(tc->mon_size); p++) {
2030+ if (tc->mon[p])
2031+ tc->mon[p] = tc->mon[p] * 7 / 8;
2032+ }
2033+ }
2034+
2035+ v = (float)x / tc->ref_level / 2;
2036+ w = (float)y / tc->ref_level / 2;
2037+
2038+ px = tc->mon_size / 2 + (v * tc->mon_size / 2);
2039+ py = tc->mon_size / 2 + (w * tc->mon_size / 2);
2040+
2041+ /* Set the pixel value to white */
2042+
2043+ if (px > 0 && px < tc->mon_size && py > 0 && py < tc->mon_size)
2044+ tc->mon[py * tc->mon_size + px] = 0xff;
2045+}
2046+
2047+
2048+/* Process a single bitstream reading */
2049+
2050+static void process_bitstream(struct timecoder_t *tc, signed int m)
2051+{
2052+ bits_t b;
2053+
2054+ b = m > tc->ref_level;
2055+
2056+ /* Add it to the bitstream, and work out what we were expecting
2057+ * (timecode). */
2058+
2059+ /* tc->bitstream is always in the order it is physically placed on
2060+ * the vinyl, regardless of the direction. */
2061+
2062+ if (tc->forwards) {
2063+ tc->timecode = fwd(tc->timecode, tc->def);
2064+ tc->bitstream = (tc->bitstream >> 1)
2065+ + (b << (tc->def->bits - 1));
2066+
2067+ } else {
2068+ bits_t mask;
2069+
2070+ mask = ((1 << tc->def->bits) - 1);
2071+ tc->timecode = rev(tc->timecode, tc->def);
2072+ tc->bitstream = ((tc->bitstream << 1) & mask) + b;
2073+ }
2074+
2075+ if (tc->timecode == tc->bitstream)
2076+ tc->valid_counter++;
2077+ else {
2078+ tc->timecode = tc->bitstream;
2079+ tc->valid_counter = 0;
2080+ }
2081+
2082+ /* Take note of the last time we read a valid timecode */
2083
2084- return swapped;
2085+ tc->timecode_ticker = 0;
2086+
2087+ /* Adjust the reference level based on this new peak */
2088+
2089+ tc->ref_level = (tc->ref_level * (REF_PEAKS_AVG - 1) + m) / REF_PEAKS_AVG;
2090+
2091+#ifdef DEBUG_BITSTREAM
2092+ fprintf(stderr, "%+6d zero, %+6d (ref %+6d)\t= %d%c (%5d)\n",
2093+ tc->primary.zero,
2094+ m,
2095+ tc->ref_level,
2096+ b,
2097+ tc->valid_counter == 0 ? 'x' : ' ',
2098+ tc->valid_counter);
2099+#endif
2100+}
2101+
2102+
2103+/* Process a single sample from the incoming audio */
2104+
2105+static void process_sample(struct timecoder_t *tc,
2106+ signed int primary, signed int secondary)
2107+{
2108+ signed int m; /* pcm sample, sum of two shorts */
2109+
2110+ detect_zero_crossing(&tc->primary, primary, tc->zero_alpha);
2111+ detect_zero_crossing(&tc->secondary, secondary, tc->zero_alpha);
2112+
2113+ m = abs(primary - tc->primary.zero);
2114+
2115+ /* If an axis has been crossed, use the direction of the crossing
2116+ * to work out the direction of the vinyl */
2117+
2118+ if (tc->primary.swapped) {
2119+ tc->forwards = (tc->primary.positive != tc->secondary.positive);
2120+ if (tc->def->flags & SWITCH_PHASE)
2121+ tc->forwards = !tc->forwards;
2122+ } if (tc->secondary.swapped) {
2123+ tc->forwards = (tc->primary.positive == tc->secondary.positive);
2124+ if (tc->def->flags & SWITCH_PHASE)
2125+ tc->forwards = !tc->forwards;
2126+ }
2127+
2128+ /* If any axis has been crossed, register movement using the pitch
2129+ * counters */
2130+
2131+ if (!tc->primary.swapped && !tc->secondary.swapped)
2132+ pitch_dt_observation(&tc->pitch, 0.0);
2133+ else {
2134+ float dx;
2135+
2136+ dx = 1.0 / tc->def->resolution / 4;
2137+ if (!tc->forwards)
2138+ dx = -dx;
2139+ pitch_dt_observation(&tc->pitch, dx);
2140+ }
2141+
2142+ /* If we have crossed the primary channel in the right polarity,
2143+ * it's time to read off a timecode 0 or 1 value */
2144+
2145+ if (tc->secondary.swapped &&
2146+ tc->primary.positive == ((tc->def->flags & SWITCH_POLARITY) == 0))
2147+ {
2148+ process_bitstream(tc, m);
2149+ }
2150+
2151+ tc->timecode_ticker++;
2152 }
2153
2154
2155 /* Submit and decode a block of PCM audio data to the timecoder */
2156
2157-int timecoder_submit(struct timecoder_t *tc, signed short *pcm, int samples)
2158+void timecoder_submit(struct timecoder_t *tc, signed short *pcm, size_t npcm)
2159 {
2160- int b, l, /* bitstream and timecode bits */
2161- s, c,
2162- x, y, p, /* monitor coordinates */
2163- v,
2164- offset,
2165- swapped,
2166- monitor_centre;
2167- signed short w; /* pcm sample values */
2168- unsigned int mask;
2169-
2170- b = 0;
2171- l = 0;
2172-
2173- mask = ((1 << tc->tc_table->bits) - 1);
2174- monitor_centre = tc->mon_size / 2;
2175-
2176- offset = 0;
2177-
2178- for(s = 0; s < samples; s++) {
2179-
2180- for(c = 0; c < TIMECODER_CHANNELS; c++)
2181- detect_zero_crossing(&tc->channel[c], pcm[offset + c], tc->rate);
2182-
2183- /* Read from the mono channel */
2184-
2185- v = pcm[offset] + pcm[offset + 1];
2186- swapped = detect_zero_crossing(&tc->mono, v, tc->rate);
2187-
2188- /* If a sign change in the (zero corrected) audio has
2189- * happened, log the peak information */
2190-
2191- if(swapped) {
2192-
2193- /* Work out whether half way through a cycle we are
2194- * looking for the wave to be positive or negative */
2195-
2196- if(tc->mono.positive == (tc->tc_table->polarity ^ tc->forwards)) {
2197-
2198- /* Entering the second half of a wave cycle */
2199-
2200- tc->half_peak = tc->wave_peak;
2201-
2202- } else {
2203-
2204- /* Completed a full wave cycle, so time to analyse the
2205- * level and work out whether it's a 1 or 0 */
2206-
2207- b = tc->wave_peak + tc->half_peak > tc->ref_level;
2208-
2209- /* Log binary timecode */
2210-
2211- if(tc->log_fd != -1)
2212- write(tc->log_fd, b ? "1" : "0", 1);
2213-
2214- /* Add it to the bitstream, and work out what we were
2215- * expecting (timecode). */
2216-
2217- /* tc->bitstream is always in the order it is
2218- * physically placed on the vinyl, regardless of the
2219- * direction. */
2220-
2221- if(tc->forwards) {
2222- l = lfsr(tc->timecode, tc);
2223-
2224- tc->bitstream = (tc->bitstream >> 1)
2225- + (b << (tc->tc_table->bits - 1));
2226-
2227- tc->timecode = (tc->timecode >> 1)
2228- + (l << (tc->tc_table->bits - 1));
2229-
2230- } else {
2231- l = lfsr_rev(tc->timecode, tc);
2232-
2233- tc->bitstream = ((tc->bitstream << 1) & mask) + b;
2234- tc->timecode = ((tc->timecode << 1) & mask) + l;
2235- }
2236-
2237- if(b == l) {
2238- tc->valid_counter++;
2239- } else {
2240- tc->timecode = tc->bitstream;
2241- tc->valid_counter = 0;
2242- }
2243-
2244- /* Take note of the last time we read a valid timecode */
2245-
2246- tc->timecode_ticker = 0;
2247-
2248- /* Adjust the reference level based on the peaks seen
2249- * in this cycle */
2250-
2251- if(tc->ref_level == -1)
2252- tc->ref_level = tc->half_peak + tc->wave_peak;
2253- else {
2254- tc->ref_level = (tc->ref_level * (REF_PEAKS_AVG - 1)
2255- + tc->half_peak + tc->wave_peak)
2256- / REF_PEAKS_AVG;
2257- }
2258-
2259- }
2260-
2261- /* Calculate the immediate direction from phase difference,
2262- * based on the last channel to cross zero */
2263-
2264- if(tc->channel[0].crossing_ticker > tc->channel[1].crossing_ticker)
2265- tc->forwards = 1;
2266- else
2267- tc->forwards = 0;
2268-
2269- if(tc->forwards)
2270- tc->crossings++;
2271- else
2272- tc->crossings--;
2273-
2274- tc->pitch_ticker += tc->crossing_ticker;
2275- tc->crossing_ticker = 0;
2276- tc->wave_peak = 0;
2277-
2278- } /* swapped */
2279-
2280- tc->crossing_ticker++;
2281- tc->timecode_ticker++;
2282-
2283- /* Find the zero-normalised sample of the peak value from
2284- * the input */
2285-
2286- w = abs(v - tc->mono.zero);
2287- if(w > tc->wave_peak)
2288- tc->wave_peak = w;
2289-
2290- /* Take a rolling average of zero and signal level */
2291-
2292- tc->signal_level += (w - tc->signal_level) * SIGNAL_AVG / tc->rate;
2293-
2294- /* Update the monitor to add the incoming sample */
2295-
2296- if(tc->mon) {
2297-
2298- /* Decay the pixels already in the montior */
2299-
2300- if(++tc->mon_counter % MONITOR_DECAY_EVERY == 0) {
2301- for(p = 0; p < SQ(tc->mon_size); p++) {
2302- if(tc->mon[p])
2303- tc->mon[p] = tc->mon[p] * 7 / 8;
2304- }
2305- }
2306-
2307- v = pcm[offset]; /* first channel */
2308- w = pcm[offset + 1]; /* second channel */
2309-
2310- x = monitor_centre + (v * tc->mon_size * tc->mon_scale / 32768);
2311- y = monitor_centre + (w * tc->mon_size * tc->mon_scale / 32768);
2312-
2313- /* Set the pixel value to white */
2314-
2315- if(x > 0 && x < tc->mon_size && y > 0 && y < tc->mon_size)
2316- tc->mon[y * tc->mon_size + x] = 0xff;
2317+ while (npcm--) {
2318+ signed int primary, secondary;
2319+
2320+ if (tc->def->flags & SWITCH_PRIMARY) {
2321+ primary = pcm[0];
2322+ secondary = pcm[1];
2323+ } else {
2324+ primary = pcm[1];
2325+ secondary = pcm[0];
2326 }
2327-
2328- offset += TIMECODER_CHANNELS;
2329-
2330- } /* for each sample */
2331-
2332- /* Print debugging information */
2333-
2334-#if 0
2335- fprintf(stderr, "%+6d +/%4d -/%4d (%4d,%4d)\t= %d (%d) %c %d"
2336- "\t[crossings: %d %d]",
2337- tc->mono.zero,
2338- tc->half_peak,
2339- tc->wave_peak,
2340- tc->ref_level >> 1,
2341- tc->signal_level,
2342- b, l, b == l ? ' ' : 'x',
2343- tc->valid_counter,
2344- tc->crossings,
2345- tc->pitch_ticker);
2346-
2347- if(tc->pitch_ticker)
2348- fprintf(stderr, " = %d", tc->rate * tc->crossings / tc->pitch_ticker);
2349-
2350- fputc('\n', stderr);
2351-#endif
2352-
2353- return 0;
2354-}
2355-
2356-
2357-/* Return the timecode pitch, based on cycles of the sine wave. This
2358- * function can only be called by one context, at it resets the state
2359- * of the counter in the timecoder. */
2360-
2361-int timecoder_get_pitch(struct timecoder_t *tc, float *pitch)
2362-{
2363- /* Let the caller know if there's no data to gather pitch from */
2364-
2365- if(tc->crossings == 0)
2366- return -1;
2367-
2368- /* Value of tc->crossings may be negative in reverse */
2369-
2370- *pitch = tc->rate * (float)tc->crossings / tc->pitch_ticker
2371- / (tc->tc_table->resolution * 2);
2372-
2373- tc->crossings = 0;
2374- tc->pitch_ticker = 0;
2375-
2376- return 0;
2377+
2378+ process_sample(tc, primary, secondary);
2379+
2380+ update_monitor(tc, pcm[0], pcm[1]);
2381+ pcm += TIMECODER_CHANNELS;
2382+ }
2383 }
2384
2385
2386 /* Return the known position in the timecode, or -1 if not known. If
2387 * two few bits have been error-checked, then this also counts as
2388- * invalid. If 'when' is given, return the time, in input samples since
2389- * this value was read. */
2390+ * invalid. If 'when' is given, return the time, in seconds since this
2391+ * value was read. */
2392
2393-signed int timecoder_get_position(struct timecoder_t *tc, int *when)
2394+signed int timecoder_get_position(struct timecoder_t *tc, float *when)
2395 {
2396 signed int r;
2397
2398- if(tc->valid_counter > VALID_BITS) {
2399- r = tc->tc_table->lookup[tc->bitstream];
2400+ if (tc->valid_counter > VALID_BITS) {
2401+ r = lut_lookup(&tc->def->lut, tc->bitstream);
2402
2403- if(r >= 0) {
2404- if(when)
2405- *when = tc->timecode_ticker;
2406+ if (r >= 0) {
2407+ if (when)
2408+ *when = tc->timecode_ticker * tc->dt;
2409 return r;
2410 }
2411 }
2412
2413 return -1;
2414 }
2415-
2416-
2417-/* Return non-zero if there is any timecode signal available */
2418-
2419-int timecoder_get_alive(struct timecoder_t *tc)
2420-{
2421- if(tc->signal_level < SIGNAL_THRESHOLD)
2422- return 0;
2423-
2424- return 1;
2425-}
2426-
2427-
2428-/* Return the last 'safe' timecode value on the record. Beyond this
2429- * value, we probably want to ignore the timecode values, as we will
2430- * hit the label of the record. */
2431-
2432-unsigned int timecoder_get_safe(struct timecoder_t *tc)
2433-{
2434- return tc->tc_table->safe;
2435-}
2436-
2437-
2438-/* Return the resolution of the timecode. This is the number of bits
2439- * per second, which corresponds to the frequency of the sine wave */
2440-
2441-int timecoder_get_resolution(struct timecoder_t *tc)
2442-{
2443- return tc->tc_table->resolution;
2444-}
2445
2446=== modified file 'mixxx/res/skins/Outline1024x600-Netbook/skin.xml'
2447--- mixxx/res/skins/Outline1024x600-Netbook/skin.xml 2011-03-22 16:24:04 +0000
2448+++ mixxx/res/skins/Outline1024x600-Netbook/skin.xml 2011-04-16 23:24:33 +0000
2449@@ -949,6 +949,83 @@
2450 <ConfigKey>[Master],PeakIndicator</ConfigKey>
2451 </Connection>
2452 </StatusLight>
2453+
2454+ <!--
2455+ **********************************************
2456+ Vinyl Stuff
2457+ **********************************************
2458+ -->
2459+
2460+ <StatusLight>
2461+ <Tooltip>Vinyl Status</Tooltip>
2462+ <NumberPos>4</NumberPos>
2463+ <PathBack>vinyl-status-disabled.png</PathBack>
2464+ <PathStatusLight>vinyl-status-green.png</PathStatusLight>
2465+ <PathStatusLight2>vinyl-status-yellow.png</PathStatusLight2>
2466+ <PathStatusLight3>vinyl-status-red.png</PathStatusLight3>
2467+ <Pos>357,194</Pos>
2468+ <Connection>
2469+ <ConfigKey>[Channel1],vinylcontrol_status</ConfigKey>
2470+ </Connection>
2471+ </StatusLight>
2472+ <StatusLight>
2473+ <Tooltip>Vinyl Status</Tooltip>
2474+ <NumberPos>4</NumberPos>
2475+ <PathBack>vinyl-status-disabled.png</PathBack>
2476+ <PathStatusLight>vinyl-status-green.png</PathStatusLight>
2477+ <PathStatusLight2>vinyl-status-yellow.png</PathStatusLight2>
2478+ <PathStatusLight3>vinyl-status-red.png</PathStatusLight3>
2479+ <Pos>597,194</Pos>
2480+ <Connection>
2481+ <ConfigKey>[Channel2],vinylcontrol_status</ConfigKey>
2482+ </Connection>
2483+ </StatusLight>
2484+ <PushButton>
2485+ <Tooltip>Vinyl Control Mode</Tooltip>
2486+ <NumberStates>3</NumberStates>
2487+ <State>
2488+ <Number>0</Number>
2489+ <Pressed>vinyl-abs.png</Pressed>
2490+ <Unpressed>vinyl-abs.png</Unpressed>
2491+ </State>
2492+ <State>
2493+ <Number>1</Number>
2494+ <Pressed>vinyl-rel.png</Pressed>
2495+ <Unpressed>vinyl-rel.png</Unpressed>
2496+ </State>
2497+ <State>
2498+ <Number>2</Number>
2499+ <Pressed>vinyl-const.png</Pressed>
2500+ <Unpressed>vinyl-const.png</Unpressed>
2501+ </State>
2502+ <Pos>380,192</Pos>
2503+ <Connection>
2504+ <ConfigKey>[Channel1],vinylcontrol_mode</ConfigKey>
2505+ </Connection>
2506+ </PushButton>
2507+ <PushButton>
2508+ <Tooltip>Vinyl Control Mode</Tooltip>
2509+ <NumberStates>3</NumberStates>
2510+ <State>
2511+ <Number>0</Number>
2512+ <Pressed>vinyl-abs.png</Pressed>
2513+ <Unpressed>vinyl-abs.png</Unpressed>
2514+ </State>
2515+ <State>
2516+ <Number>1</Number>
2517+ <Pressed>vinyl-rel.png</Pressed>
2518+ <Unpressed>vinyl-rel.png</Unpressed>
2519+ </State>
2520+ <State>
2521+ <Number>2</Number>
2522+ <Pressed>vinyl-const.png</Pressed>
2523+ <Unpressed>vinyl-const.png</Unpressed>
2524+ </State>
2525+ <Pos>621,192</Pos>
2526+ <Connection>
2527+ <ConfigKey>[Channel2],vinylcontrol_mode</ConfigKey>
2528+ </Connection>
2529+ </PushButton>
2530
2531 <!--
2532 ############################################################################################
2533
2534=== added file 'mixxx/res/skins/Outline1024x600-Netbook/vinyl-abs.png'
2535Binary files mixxx/res/skins/Outline1024x600-Netbook/vinyl-abs.png 1970-01-01 00:00:00 +0000 and mixxx/res/skins/Outline1024x600-Netbook/vinyl-abs.png 2011-04-16 23:24:33 +0000 differ
2536=== added file 'mixxx/res/skins/Outline1024x600-Netbook/vinyl-const.png'
2537Binary files mixxx/res/skins/Outline1024x600-Netbook/vinyl-const.png 1970-01-01 00:00:00 +0000 and mixxx/res/skins/Outline1024x600-Netbook/vinyl-const.png 2011-04-16 23:24:33 +0000 differ
2538=== added file 'mixxx/res/skins/Outline1024x600-Netbook/vinyl-rel.png'
2539Binary files mixxx/res/skins/Outline1024x600-Netbook/vinyl-rel.png 1970-01-01 00:00:00 +0000 and mixxx/res/skins/Outline1024x600-Netbook/vinyl-rel.png 2011-04-16 23:24:33 +0000 differ
2540=== added file 'mixxx/res/skins/Outline1024x600-Netbook/vinyl-status-disabled.png'
2541Binary files mixxx/res/skins/Outline1024x600-Netbook/vinyl-status-disabled.png 1970-01-01 00:00:00 +0000 and mixxx/res/skins/Outline1024x600-Netbook/vinyl-status-disabled.png 2011-04-16 23:24:33 +0000 differ
2542=== added file 'mixxx/res/skins/Outline1024x600-Netbook/vinyl-status-green.png'
2543Binary files mixxx/res/skins/Outline1024x600-Netbook/vinyl-status-green.png 1970-01-01 00:00:00 +0000 and mixxx/res/skins/Outline1024x600-Netbook/vinyl-status-green.png 2011-04-16 23:24:33 +0000 differ
2544=== added file 'mixxx/res/skins/Outline1024x600-Netbook/vinyl-status-red.png'
2545Binary files mixxx/res/skins/Outline1024x600-Netbook/vinyl-status-red.png 1970-01-01 00:00:00 +0000 and mixxx/res/skins/Outline1024x600-Netbook/vinyl-status-red.png 2011-04-16 23:24:33 +0000 differ
2546=== added file 'mixxx/res/skins/Outline1024x600-Netbook/vinyl-status-yellow.png'
2547Binary files mixxx/res/skins/Outline1024x600-Netbook/vinyl-status-yellow.png 1970-01-01 00:00:00 +0000 and mixxx/res/skins/Outline1024x600-Netbook/vinyl-status-yellow.png 2011-04-16 23:24:33 +0000 differ
2548=== modified file 'mixxx/src/cachingreader.cpp'
2549--- mixxx/src/cachingreader.cpp 2010-10-23 08:41:23 +0000
2550+++ mixxx/src/cachingreader.cpp 2011-04-16 23:24:33 +0000
2551@@ -296,8 +296,9 @@
2552 }
2553
2554 int CachingReader::read(int sample, int num_samples, CSAMPLE* buffer) {
2555+ int zerosWritten = 0;
2556 // Check for bogus sample numbers
2557- Q_ASSERT(sample >= 0);
2558+ //Q_ASSERT(sample >= 0);
2559 Q_ASSERT(sample % 2 == 0);
2560 Q_ASSERT(num_samples >= 0);
2561
2562@@ -309,6 +310,27 @@
2563 return 0;
2564 }
2565
2566+ // TODO: is it possible to move this code out of caching reader
2567+ // and into enginebuffer? It doesn't quite make sense here, although
2568+ // it makes preroll completely transparent to the rest of the code
2569+
2570+ //if we're in preroll...
2571+ if (sample < 0) {
2572+ if (sample + num_samples <= 0) {
2573+ //everything is zeros, easy
2574+ memset(buffer, 0, sizeof(*buffer) * num_samples);
2575+ return num_samples;
2576+ } else {
2577+ //some of the buffer is zeros, some is from the file
2578+ memset(buffer, 0, sizeof(*buffer) * (0 - sample));
2579+ buffer += (0 - sample);
2580+ num_samples = sample + num_samples;
2581+ zerosWritten = (0 - sample);
2582+ sample = 0;
2583+ //continue processing the rest of the chunks normally
2584+ }
2585+ }
2586+
2587 int start_chunk = chunkForSample(sample);
2588 int end_chunk = chunkForSample(sample + num_samples - 1);
2589
2590@@ -400,7 +422,7 @@
2591 samples_remaining = 0;
2592
2593 Q_ASSERT(samples_remaining == 0);
2594- return num_samples - samples_remaining;
2595+ return zerosWritten + num_samples - samples_remaining;
2596 }
2597
2598 void CachingReader::hint(Hint& hint) {
2599@@ -448,7 +470,7 @@
2600 hint.sample = 0;
2601 }
2602 }
2603- Q_ASSERT(hint.sample >= 0);
2604+ //Q_ASSERT(hint.sample >= 0);
2605 Q_ASSERT(hint.length >= 0);
2606 int start_chunk = chunkForSample(hint.sample);
2607 int end_chunk = chunkForSample(hint.sample + hint.length);
2608
2609=== modified file 'mixxx/src/controlobject.cpp'
2610--- mixxx/src/controlobject.cpp 2011-04-04 05:36:08 +0000
2611+++ mixxx/src/controlobject.cpp 2011-04-16 23:24:33 +0000
2612@@ -293,8 +293,11 @@
2613 {
2614 obj = m_sqQueueThread.dequeue();
2615
2616- obj->pControlObject->setValueFromThread(obj->value);
2617- obj->pControlObject->updateProxies(obj->pControlObjectThread);
2618+ if (obj->pControlObject)
2619+ {
2620+ obj->pControlObject->setValueFromThread(obj->value);
2621+ obj->pControlObject->updateProxies(obj->pControlObjectThread);
2622+ }
2623 delete obj;
2624 }
2625
2626
2627=== modified file 'mixxx/src/controlpushbutton.cpp'
2628--- mixxx/src/controlpushbutton.cpp 2010-09-17 02:20:29 +0000
2629+++ mixxx/src/controlpushbutton.cpp 2011-04-16 23:24:33 +0000
2630@@ -24,6 +24,7 @@
2631 ControlPushButton::ControlPushButton(ConfigKey key) :
2632 ControlObject(key, false) {
2633 m_bIsToggleButton = false;
2634+ m_iNoStates = 2;
2635 }
2636
2637 ControlPushButton::~ControlPushButton()
2638@@ -37,6 +38,11 @@
2639 m_bIsToggleButton = bIsToggleButton;
2640 }
2641
2642+void ControlPushButton::setStates(int num_states)
2643+{
2644+ m_iNoStates = num_states;
2645+}
2646+
2647 void ControlPushButton::setValueFromMidi(MidiCategory c, double v)
2648 {
2649 //if (m_bMidiSimulateLatching)
2650@@ -44,55 +50,30 @@
2651 //qDebug() << "bMidiSimulateLatching is true!";
2652 // Only react on NOTE_ON midi events if simulating latching...
2653
2654+ //qDebug() << c << v;
2655
2656- if (m_bIsToggleButton) //This block makes push-buttons act as toggle buttons.
2657- {
2658- //qDebug() << "Is a toggle button!";
2659- if (c==NOTE_ON && v>0.) //Only react to "NOTE_ON" midi events.
2660- {
2661- //qDebug() << "NOTE_ON caught!";
2662- if (m_dValue==0.)
2663- m_dValue = 1.;
2664- else
2665- m_dValue = 0.;
2666+ if (m_bIsToggleButton) { //This block makes push-buttons act as toggle buttons.
2667+ if (m_iNoStates > 2) { //multistate button
2668+ if (v > 0.) { //looking for NOTE_ON doesn't seem to work...
2669+ m_dValue++;
2670+ if (m_dValue >= m_iNoStates)
2671+ m_dValue = 0;
2672+ }
2673+ } else {
2674+ if (c == NOTE_ON) {
2675+ if (v > 0.) {
2676+ m_dValue = !m_dValue;
2677+ }
2678+ }
2679 }
2680- }
2681- else //Not a toggle button (trigger only when button pushed)
2682- {
2683- //qDebug() << "Is NOT a toggle button!";
2684- if (c == NOTE_ON)
2685+ } else { //Not a toggle button (trigger only when button pushed)
2686+ if (c == NOTE_ON) {
2687 m_dValue = v;
2688- else if (c == NOTE_OFF)
2689+ } else if (c == NOTE_OFF) {
2690 m_dValue = 0.0;
2691- }
2692- if (c==NOTE_OFF)
2693- {
2694-
2695- //qDebug() << "NOTE_OFF caught!";
2696- }
2697-
2698-
2699-/* else
2700- {
2701- qDebug() << "bMidiSimulateLatching is false!";
2702- qDebug() << "m_dValue is: " << m_dValue << ", v is: " << v;
2703-
2704- if (v==0.)
2705- m_dValue = 0.;
2706- else
2707- m_dValue = 1.;
2708-
2709- }
2710- */
2711-/*
2712- if (v>0.)
2713- {
2714- if (m_dValue==0.)
2715- m_dValue = 1.;
2716- else
2717- m_dValue = 0.;
2718 }
2719- */
2720+ }
2721+
2722 emit(valueChanged(m_dValue));
2723 }
2724
2725
2726=== modified file 'mixxx/src/controlpushbutton.h'
2727--- mixxx/src/controlpushbutton.h 2010-10-07 03:05:48 +0000
2728+++ mixxx/src/controlpushbutton.h 2011-04-16 23:24:33 +0000
2729@@ -32,12 +32,14 @@
2730 ControlPushButton(ConfigKey key);
2731 ~ControlPushButton();
2732 void setToggleButton(bool bIsToggleButton);
2733+ void setStates(int num_states);
2734
2735 protected:
2736 void setValueFromMidi(MidiCategory c, double v);
2737
2738 private:
2739 bool m_bIsToggleButton;
2740+ int m_iNoStates;
2741 };
2742
2743 #endif
2744
2745=== modified file 'mixxx/src/controlvaluedelegate.cpp'
2746--- mixxx/src/controlvaluedelegate.cpp 2010-11-19 11:03:20 +0000
2747+++ mixxx/src/controlvaluedelegate.cpp 2011-04-16 23:24:33 +0000
2748@@ -55,6 +55,9 @@
2749 m_channelControlValues.append("volume");
2750 m_channelControlValues.append("wheel");
2751 m_channelControlValues.append("jog");
2752+ m_channelControlValues.append("vinylcontrol_enabled");
2753+ m_channelControlValues.append("vinylcontrol_mode");
2754+ m_channelControlValues.append("vinylcontrol_cueing");
2755 m_channelControlValues.append("loop_in");
2756 m_channelControlValues.append("loop_out");
2757 m_channelControlValues.append("reloop_exit");
2758
2759=== modified file 'mixxx/src/dlgpreferences.cpp'
2760--- mixxx/src/dlgpreferences.cpp 2011-03-17 06:49:23 +0000
2761+++ mixxx/src/dlgpreferences.cpp 2011-04-16 23:24:33 +0000
2762@@ -18,6 +18,8 @@
2763
2764 #ifdef __VINYLCONTROL__
2765 #include "dlgprefvinyl.h"
2766+#else
2767+#include "dlgprefnovinyl.h"
2768 #endif
2769
2770 #ifdef __SHOUTCAST__
2771@@ -75,6 +77,8 @@
2772 wrecord = new DlgPrefRecord(this, config);
2773 #ifdef __VINYLCONTROL__
2774 wvinylcontrol = new DlgPrefVinyl(this, soundman, config);
2775+#else
2776+ wnovinylcontrol = new DlgPrefNoVinyl(this, soundman, config);
2777 #endif
2778 #ifdef __SHOUTCAST__
2779 wshoutcast = new DlgPrefShoutcast(this, config);
2780@@ -96,6 +100,8 @@
2781 pagesWidget->addWidget(wreplaygain);
2782 #ifdef __VINYLCONTROL__
2783 pagesWidget->addWidget(wvinylcontrol);
2784+#else
2785+ pagesWidget->addWidget(wnovinylcontrol);
2786 #endif
2787 #ifdef __SHOUTCAST__
2788 pagesWidget->addWidget(wshoutcast);
2789@@ -134,6 +140,7 @@
2790
2791 #ifdef __VINYLCONTROL__
2792 connect(wvinylcontrol, SIGNAL(refreshVCProxies()), wsound, SLOT(forceApply()));
2793+ connect(wvinylcontrol, SIGNAL(applySound()), wsound, SLOT(slotApply()));
2794 connect(buttonBox, SIGNAL(accepted()), wvinylcontrol, SLOT(slotApply())); //It's important for this to be before the
2795 //connect for wsound...
2796 #endif
2797@@ -242,6 +249,14 @@
2798 m_pVinylControlButton->setText(0, tr("Vinyl Control"));
2799 m_pVinylControlButton->setTextAlignment(0, Qt::AlignLeft | Qt::AlignVCenter);
2800 m_pVinylControlButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
2801+#else
2802+ m_pVinylControlButton = new QTreeWidgetItem(contentsTreeWidget, QTreeWidgetItem::Type);
2803+ //QT screws up my nice vinyl svg for some reason, so we'll use a PNG version
2804+ //instead...
2805+ m_pVinylControlButton->setIcon(0, QIcon(":/images/preferences/ic_preferences_vinyl.png"));
2806+ m_pVinylControlButton->setText(0, tr("Vinyl Control"));
2807+ m_pVinylControlButton->setTextAlignment(0, Qt::AlignLeft | Qt::AlignVCenter);
2808+ m_pVinylControlButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
2809 #endif
2810
2811 #ifdef __SHOUTCAST__
2812@@ -277,11 +292,14 @@
2813 else if (current == m_pBPMdetectButton)
2814 pagesWidget->setCurrentWidget(wbpm);
2815 else if (current == m_pReplayGainButton)
2816- pagesWidget->setCurrentWidget(wreplaygain);
2817+ pagesWidget->setCurrentWidget(wreplaygain);
2818
2819 #ifdef __VINYLCONTROL__
2820 else if (current == m_pVinylControlButton)
2821 pagesWidget->setCurrentWidget(wvinylcontrol);
2822+#else
2823+ else if (current == m_pVinylControlButton)
2824+ pagesWidget->setCurrentWidget(wnovinylcontrol);
2825 #endif
2826 #ifdef __SHOUTCAST__
2827 else if (current == m_pShoutcastButton)
2828
2829=== modified file 'mixxx/src/dlgpreferences.h'
2830--- mixxx/src/dlgpreferences.h 2011-03-17 06:49:23 +0000
2831+++ mixxx/src/dlgpreferences.h 2011-04-16 23:24:33 +0000
2832@@ -42,6 +42,7 @@
2833 class DlgPrefRecord;
2834 class DlgPrefBpm;
2835 class DlgPrefVinyl;
2836+class DlgPrefNoVinyl;
2837 class DlgPrefShoutcast;
2838 class DlgPrefReplayGain;
2839 class PowerMate;
2840@@ -89,19 +90,20 @@
2841 DlgPrefRecord *wrecord;
2842 DlgPrefBpm *wbpm;
2843 DlgPrefVinyl *wvinylcontrol;
2844+ DlgPrefNoVinyl *wnovinylcontrol;
2845 DlgPrefShoutcast *wshoutcast;
2846 DlgPrefReplayGain *wreplaygain;
2847
2848- QTreeWidgetItem* m_pSoundButton;
2849- QTreeWidgetItem* m_pPlaylistButton;
2850- QTreeWidgetItem* m_pControlsButton;
2851- QTreeWidgetItem* m_pEqButton;
2852- QTreeWidgetItem* m_pCrossfaderButton;
2853- QTreeWidgetItem* m_pRecordingButton;
2854- QTreeWidgetItem* m_pBPMdetectButton;
2855- QTreeWidgetItem* m_pVinylControlButton;
2856- QTreeWidgetItem* m_pShoutcastButton;
2857- QTreeWidgetItem* m_pReplayGainButton;
2858+ QTreeWidgetItem* m_pSoundButton;
2859+ QTreeWidgetItem* m_pPlaylistButton;
2860+ QTreeWidgetItem* m_pControlsButton;
2861+ QTreeWidgetItem* m_pEqButton;
2862+ QTreeWidgetItem* m_pCrossfaderButton;
2863+ QTreeWidgetItem* m_pRecordingButton;
2864+ QTreeWidgetItem* m_pBPMdetectButton;
2865+ QTreeWidgetItem* m_pVinylControlButton;
2866+ QTreeWidgetItem* m_pShoutcastButton;
2867+ QTreeWidgetItem* m_pReplayGainButton;
2868 QTreeWidgetItem* m_pMIDITreeItem;
2869 QList<QTreeWidgetItem*> m_midiBindingsButtons;
2870
2871
2872=== added file 'mixxx/src/dlgprefnovinyl.cpp'
2873--- mixxx/src/dlgprefnovinyl.cpp 1970-01-01 00:00:00 +0000
2874+++ mixxx/src/dlgprefnovinyl.cpp 2011-04-16 23:24:33 +0000
2875@@ -0,0 +1,32 @@
2876+/***************************************************************************
2877+ DlgPrefNoVinyl.cpp - description
2878+ -------------------
2879+ begin : Thu Feb 24 2011
2880+ copyright : (C) 2011 by Owen Williams
2881+ email : owen-bugs@ywwg.com
2882+***************************************************************************/
2883+
2884+/***************************************************************************
2885+* *
2886+* This program is free software; you can redistribute it and/or modify *
2887+* it under the terms of the GNU General Public License as published by *
2888+* the Free Software Foundation; either version 2 of the License, or *
2889+* (at your option) any later version. *
2890+* *
2891+***************************************************************************/
2892+
2893+
2894+#include <QtCore>
2895+#include <QtDebug>
2896+#include <QtGui>
2897+#include "dlgprefnovinyl.h"
2898+
2899+DlgPrefNoVinyl::DlgPrefNoVinyl(QWidget * parent, SoundManager * soundman,
2900+ ConfigObject<ConfigValue> * _config) : QWidget(parent), Ui::DlgPrefNoVinylDlg()
2901+{
2902+ setupUi(this);
2903+}
2904+
2905+DlgPrefNoVinyl::~DlgPrefNoVinyl()
2906+{
2907+}
2908
2909=== added file 'mixxx/src/dlgprefnovinyl.h'
2910--- mixxx/src/dlgprefnovinyl.h 1970-01-01 00:00:00 +0000
2911+++ mixxx/src/dlgprefnovinyl.h 2011-04-16 23:24:33 +0000
2912@@ -0,0 +1,39 @@
2913+/***************************************************************************
2914+ dlgprefnovinyl.h - description
2915+ -------------------
2916+ begin : Thu Feb 24 2011
2917+ copyright : (C) 2011 by Owen Williams
2918+ email : owen-bugs@ywwg.com
2919+ ***************************************************************************/
2920+
2921+/***************************************************************************
2922+ * *
2923+ * This program is free software; you can redistribute it and/or modify *
2924+ * it under the terms of the GNU General Public License as published by *
2925+ * the Free Software Foundation; either version 2 of the License, or *
2926+ * (at your option) any later version. *
2927+ * *
2928+ ***************************************************************************/
2929+
2930+#ifndef DLGPREFNOVINYL_H
2931+#define DLGPREFNOVINYL_H
2932+
2933+#include "ui_dlgprefnovinyldlg.h"
2934+#include "configobject.h"
2935+
2936+class QWidget;
2937+class SoundManager;
2938+
2939+/**
2940+ *@author Stefan Langhammer
2941+ *@author Albert Santoni
2942+ */
2943+
2944+class DlgPrefNoVinyl : public QWidget, Ui::DlgPrefNoVinylDlg {
2945+ Q_OBJECT
2946+public:
2947+ DlgPrefNoVinyl(QWidget *parent, SoundManager* soundman, ConfigObject<ConfigValue> *_config);
2948+ ~DlgPrefNoVinyl();
2949+};
2950+
2951+#endif
2952
2953=== added file 'mixxx/src/dlgprefnovinyldlg.ui'
2954--- mixxx/src/dlgprefnovinyldlg.ui 1970-01-01 00:00:00 +0000
2955+++ mixxx/src/dlgprefnovinyldlg.ui 2011-04-16 23:24:33 +0000
2956@@ -0,0 +1,475 @@
2957+<?xml version="1.0" encoding="UTF-8"?>
2958+<ui version="4.0">
2959+ <class>DlgPrefNoVinylDlg</class>
2960+ <widget class="QWidget" name="DlgPrefNoVinylDlg">
2961+ <property name="enabled">
2962+ <bool>true</bool>
2963+ </property>
2964+ <property name="geometry">
2965+ <rect>
2966+ <x>0</x>
2967+ <y>0</y>
2968+ <width>514</width>
2969+ <height>547</height>
2970+ </rect>
2971+ </property>
2972+ <property name="windowTitle">
2973+ <string>Form1</string>
2974+ </property>
2975+ <layout class="QGridLayout" name="gridLayout_3">
2976+ <item row="0" column="0" colspan="3">
2977+ <widget class="QGroupBox" name="groupBox_2">
2978+ <property name="enabled">
2979+ <bool>false</bool>
2980+ </property>
2981+ <property name="title">
2982+ <string>Input</string>
2983+ </property>
2984+ <layout class="QVBoxLayout" name="verticalLayout">
2985+ <item>
2986+ <widget class="QLabel" name="devicesLabel">
2987+ <property name="text">
2988+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
2989+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
2990+p, li { white-space: pre-wrap; }
2991+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
2992+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Select sound devices for vinyl control in the Sound Hardware pane.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
2993+ </property>
2994+ </widget>
2995+ </item>
2996+ <item>
2997+ <widget class="QLabel" name="label_2">
2998+ <property name="text">
2999+ <string>&lt;a href=&quot;http://www.mixxx.org/wiki/doku.php/vinyl_control#troubleshooting&quot;&gt;Troubleshooting&lt;/a&gt;</string>
3000+ </property>
3001+ <property name="openExternalLinks">
3002+ <bool>true</bool>
3003+ </property>
3004+ </widget>
3005+ </item>
3006+ <item>
3007+ <layout class="QHBoxLayout" name="horizontalLayout_2">
3008+ <item>
3009+ <widget class="QLabel" name="textLabel1_3">
3010+ <property name="text">
3011+ <string>Turntable Preamp</string>
3012+ </property>
3013+ <property name="wordWrap">
3014+ <bool>false</bool>
3015+ </property>
3016+ </widget>
3017+ </item>
3018+ <item>
3019+ <widget class="QSlider" name="VinylGain">
3020+ <property name="minimum">
3021+ <number>1</number>
3022+ </property>
3023+ <property name="maximum">
3024+ <number>150</number>
3025+ </property>
3026+ <property name="orientation">
3027+ <enum>Qt::Horizontal</enum>
3028+ </property>
3029+ </widget>
3030+ </item>
3031+ </layout>
3032+ </item>
3033+ <item>
3034+ <layout class="QHBoxLayout" name="horizontalLayout">
3035+ <item>
3036+ <spacer name="horizontalSpacer_3">
3037+ <property name="orientation">
3038+ <enum>Qt::Horizontal</enum>
3039+ </property>
3040+ <property name="sizeType">
3041+ <enum>QSizePolicy::Fixed</enum>
3042+ </property>
3043+ <property name="sizeHint" stdset="0">
3044+ <size>
3045+ <width>100</width>
3046+ <height>20</height>
3047+ </size>
3048+ </property>
3049+ </spacer>
3050+ </item>
3051+ <item>
3052+ <widget class="QLabel" name="textLabel2">
3053+ <property name="text">
3054+ <string>1 (Off)</string>
3055+ </property>
3056+ <property name="wordWrap">
3057+ <bool>false</bool>
3058+ </property>
3059+ </widget>
3060+ </item>
3061+ <item>
3062+ <spacer name="horizontalSpacer">
3063+ <property name="orientation">
3064+ <enum>Qt::Horizontal</enum>
3065+ </property>
3066+ <property name="sizeHint" stdset="0">
3067+ <size>
3068+ <width>40</width>
3069+ <height>20</height>
3070+ </size>
3071+ </property>
3072+ </spacer>
3073+ </item>
3074+ <item>
3075+ <widget class="QLabel" name="textLabel3">
3076+ <property name="sizePolicy">
3077+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
3078+ <horstretch>0</horstretch>
3079+ <verstretch>0</verstretch>
3080+ </sizepolicy>
3081+ </property>
3082+ <property name="text">
3083+ <string>150</string>
3084+ </property>
3085+ <property name="wordWrap">
3086+ <bool>false</bool>
3087+ </property>
3088+ </widget>
3089+ </item>
3090+ </layout>
3091+ </item>
3092+ </layout>
3093+ </widget>
3094+ </item>
3095+ <item row="1" column="0" colspan="3">
3096+ <widget class="QGroupBox" name="groupBox_3">
3097+ <property name="enabled">
3098+ <bool>false</bool>
3099+ </property>
3100+ <property name="title">
3101+ <string>Vinyl Configuration</string>
3102+ </property>
3103+ <layout class="QGridLayout" name="gridLayout_2">
3104+ <item row="0" column="0">
3105+ <widget class="QLabel" name="TextLabel29">
3106+ <property name="enabled">
3107+ <bool>false</bool>
3108+ </property>
3109+ <property name="sizePolicy">
3110+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
3111+ <horstretch>0</horstretch>
3112+ <verstretch>0</verstretch>
3113+ </sizepolicy>
3114+ </property>
3115+ <property name="font">
3116+ <font/>
3117+ </property>
3118+ <property name="text">
3119+ <string>Deck 1 Vinyl Type</string>
3120+ </property>
3121+ <property name="wordWrap">
3122+ <bool>false</bool>
3123+ </property>
3124+ </widget>
3125+ </item>
3126+ <item row="0" column="2">
3127+ <widget class="QComboBox" name="ComboBoxVinylType1">
3128+ <property name="enabled">
3129+ <bool>false</bool>
3130+ </property>
3131+ <property name="sizePolicy">
3132+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
3133+ <horstretch>0</horstretch>
3134+ <verstretch>0</verstretch>
3135+ </sizepolicy>
3136+ </property>
3137+ <property name="font">
3138+ <font/>
3139+ </property>
3140+ </widget>
3141+ </item>
3142+ <item row="0" column="3">
3143+ <widget class="QComboBox" name="ComboBoxVinylSpeed1">
3144+ <property name="enabled">
3145+ <bool>false</bool>
3146+ </property>
3147+ <property name="sizePolicy">
3148+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
3149+ <horstretch>0</horstretch>
3150+ <verstretch>0</verstretch>
3151+ </sizepolicy>
3152+ </property>
3153+ <property name="font">
3154+ <font/>
3155+ </property>
3156+ </widget>
3157+ </item>
3158+ <item row="1" column="0">
3159+ <widget class="QLabel" name="TextLabel28">
3160+ <property name="enabled">
3161+ <bool>false</bool>
3162+ </property>
3163+ <property name="sizePolicy">
3164+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
3165+ <horstretch>0</horstretch>
3166+ <verstretch>0</verstretch>
3167+ </sizepolicy>
3168+ </property>
3169+ <property name="font">
3170+ <font/>
3171+ </property>
3172+ <property name="text">
3173+ <string>Deck 2 Vinyl Type</string>
3174+ </property>
3175+ <property name="wordWrap">
3176+ <bool>false</bool>
3177+ </property>
3178+ </widget>
3179+ </item>
3180+ <item row="1" column="2">
3181+ <widget class="QComboBox" name="ComboBoxVinylType2">
3182+ <property name="enabled">
3183+ <bool>false</bool>
3184+ </property>
3185+ <property name="sizePolicy">
3186+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
3187+ <horstretch>0</horstretch>
3188+ <verstretch>0</verstretch>
3189+ </sizepolicy>
3190+ </property>
3191+ <property name="font">
3192+ <font/>
3193+ </property>
3194+ </widget>
3195+ </item>
3196+ <item row="1" column="3">
3197+ <widget class="QComboBox" name="ComboBoxVinylSpeed2">
3198+ <property name="enabled">
3199+ <bool>false</bool>
3200+ </property>
3201+ <property name="sizePolicy">
3202+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
3203+ <horstretch>0</horstretch>
3204+ <verstretch>0</verstretch>
3205+ </sizepolicy>
3206+ </property>
3207+ <property name="font">
3208+ <font/>
3209+ </property>
3210+ </widget>
3211+ </item>
3212+ <item row="2" column="0">
3213+ <widget class="QLabel" name="TextLabel2_2">
3214+ <property name="enabled">
3215+ <bool>false</bool>
3216+ </property>
3217+ <property name="sizePolicy">
3218+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
3219+ <horstretch>0</horstretch>
3220+ <verstretch>0</verstretch>
3221+ </sizepolicy>
3222+ </property>
3223+ <property name="font">
3224+ <font/>
3225+ </property>
3226+ <property name="text">
3227+ <string>Lead-in time</string>
3228+ </property>
3229+ <property name="wordWrap">
3230+ <bool>false</bool>
3231+ </property>
3232+ </widget>
3233+ </item>
3234+ <item row="2" column="2">
3235+ <widget class="QLineEdit" name="LeadinTime">
3236+ <property name="sizePolicy">
3237+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
3238+ <horstretch>0</horstretch>
3239+ <verstretch>0</verstretch>
3240+ </sizepolicy>
3241+ </property>
3242+ </widget>
3243+ </item>
3244+ <item row="2" column="3">
3245+ <widget class="QLabel" name="TextLabel2_2_2">
3246+ <property name="enabled">
3247+ <bool>false</bool>
3248+ </property>
3249+ <property name="font">
3250+ <font/>
3251+ </property>
3252+ <property name="text">
3253+ <string>seconds</string>
3254+ </property>
3255+ <property name="wordWrap">
3256+ <bool>false</bool>
3257+ </property>
3258+ </widget>
3259+ </item>
3260+ <item row="3" column="1">
3261+ <spacer name="horizontalSpacer_2">
3262+ <property name="orientation">
3263+ <enum>Qt::Horizontal</enum>
3264+ </property>
3265+ <property name="sizeHint" stdset="0">
3266+ <size>
3267+ <width>40</width>
3268+ <height>20</height>
3269+ </size>
3270+ </property>
3271+ </spacer>
3272+ </item>
3273+ </layout>
3274+ </widget>
3275+ </item>
3276+ <item row="2" column="0" rowspan="3">
3277+ <widget class="QGroupBox" name="groupBox">
3278+ <property name="enabled">
3279+ <bool>false</bool>
3280+ </property>
3281+ <property name="sizePolicy">
3282+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
3283+ <horstretch>0</horstretch>
3284+ <verstretch>0</verstretch>
3285+ </sizepolicy>
3286+ </property>
3287+ <property name="maximumSize">
3288+ <size>
3289+ <width>16777215</width>
3290+ <height>16777215</height>
3291+ </size>
3292+ </property>
3293+ <property name="title">
3294+ <string>Control Mode</string>
3295+ </property>
3296+ <property name="alignment">
3297+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
3298+ </property>
3299+ <layout class="QVBoxLayout">
3300+ <item>
3301+ <widget class="QRadioButton" name="AbsoluteMode">
3302+ <property name="text">
3303+ <string>Absolute Mode</string>
3304+ </property>
3305+ </widget>
3306+ </item>
3307+ <item>
3308+ <widget class="QRadioButton" name="RelativeMode">
3309+ <property name="text">
3310+ <string>Relative Mode</string>
3311+ </property>
3312+ </widget>
3313+ </item>
3314+ <item>
3315+ <widget class="QCheckBox" name="NeedleSkipEnable">
3316+ <property name="text">
3317+ <string>Enable Needle Skip Prevention</string>
3318+ </property>
3319+ </widget>
3320+ </item>
3321+ <item>
3322+ <spacer name="verticalSpacer_3">
3323+ <property name="orientation">
3324+ <enum>Qt::Vertical</enum>
3325+ </property>
3326+ <property name="sizeHint" stdset="0">
3327+ <size>
3328+ <width>20</width>
3329+ <height>40</height>
3330+ </size>
3331+ </property>
3332+ </spacer>
3333+ </item>
3334+ </layout>
3335+ </widget>
3336+ </item>
3337+ <item row="2" column="1" colspan="2">
3338+ <widget class="QGroupBox" name="groupBoxSignalQuality">
3339+ <property name="enabled">
3340+ <bool>false</bool>
3341+ </property>
3342+ <property name="title">
3343+ <string>Signal Quality</string>
3344+ </property>
3345+ <property name="alignment">
3346+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
3347+ </property>
3348+ <layout class="QGridLayout">
3349+ <item row="0" column="0">
3350+ <spacer name="verticalSpacer_2">
3351+ <property name="orientation">
3352+ <enum>Qt::Vertical</enum>
3353+ </property>
3354+ <property name="sizeHint" stdset="0">
3355+ <size>
3356+ <width>20</width>
3357+ <height>40</height>
3358+ </size>
3359+ </property>
3360+ </spacer>
3361+ </item>
3362+ </layout>
3363+ </widget>
3364+ </item>
3365+ <item row="3" column="1" colspan="2">
3366+ <widget class="QLabel" name="label">
3367+ <property name="enabled">
3368+ <bool>false</bool>
3369+ </property>
3370+ <property name="toolTip">
3371+ <string>http://www.xwax.co.uk</string>
3372+ </property>
3373+ <property name="text">
3374+ <string>Powered by xwax</string>
3375+ </property>
3376+ <property name="alignment">
3377+ <set>Qt::AlignBottom|Qt::AlignRight|Qt::AlignTrailing</set>
3378+ </property>
3379+ </widget>
3380+ </item>
3381+ <item row="4" column="2">
3382+ <widget class="QPushButton" name="applyButton">
3383+ <property name="enabled">
3384+ <bool>false</bool>
3385+ </property>
3386+ <property name="sizePolicy">
3387+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
3388+ <horstretch>0</horstretch>
3389+ <verstretch>0</verstretch>
3390+ </sizepolicy>
3391+ </property>
3392+ <property name="sizeHint" stdset="0">
3393+ <size>
3394+ <width>85</width>
3395+ <height>27</height>
3396+ </size>
3397+ </property>
3398+ <property name="text">
3399+ <string>Apply</string>
3400+ </property>
3401+ </widget>
3402+ </item>
3403+ <item row="5" column="2">
3404+ <spacer name="verticalSpacer">
3405+ <property name="orientation">
3406+ <enum>Qt::Vertical</enum>
3407+ </property>
3408+ <property name="sizeHint" stdset="0">
3409+ <size>
3410+ <width>0</width>
3411+ <height>0</height>
3412+ </size>
3413+ </property>
3414+ </spacer>
3415+ </item>
3416+ <item row="5" column="0" colspan="2">
3417+ <widget class="QLabel" name="label_3">
3418+ <property name="text">
3419+ <string>&lt;b&gt;This version of Mixxx does not support vinyl control.&lt;/b&gt; &lt;br&gt; Please visit &lt;a href=&quot;http://mixxx.org&quot;&gt;Mixxx.org&lt;/a&gt; for more information.</string>
3420+ </property>
3421+ <property name="wordWrap">
3422+ <bool>true</bool>
3423+ </property>
3424+ </widget>
3425+ </item>
3426+ </layout>
3427+ </widget>
3428+ <layoutdefault spacing="6" margin="11"/>
3429+ <resources/>
3430+ <connections/>
3431+</ui>
3432
3433=== modified file 'mixxx/src/dlgprefsound.cpp'
3434--- mixxx/src/dlgprefsound.cpp 2011-04-08 06:04:27 +0000
3435+++ mixxx/src/dlgprefsound.cpp 2011-04-16 23:24:33 +0000
3436@@ -120,8 +120,11 @@
3437 #ifdef __VINYLCONTROL__
3438 // Scratchlib sucks, throw rocks at it
3439 // XXX(bkgood) HACKS DELETE THIS WHEN SCRATCHLIB GETS NUKED KTHX
3440- if (m_pConfig->getValueString(ConfigKey("[VinylControl]", "strVinylType"))
3441- == MIXXX_VINYL_FINALSCRATCH &&
3442+ if ((m_pConfig->getValueString(ConfigKey("[Channel1]", "vinylcontrol_vinyl_type"))
3443+ == MIXXX_VINYL_FINALSCRATCH ||
3444+ m_pConfig->getValueString(ConfigKey("[Channel2]", "vinylcontrol_vinyl_type"))
3445+ == MIXXX_VINYL_FINALSCRATCH)
3446+ &&
3447 sampleRateComboBox->itemData(sampleRateComboBox->currentIndex()).toUInt()
3448 != 44100) {
3449 QMessageBox::warning(this, tr("Mixxx Error"),
3450
3451=== modified file 'mixxx/src/dlgprefvinyl.cpp'
3452--- mixxx/src/dlgprefvinyl.cpp 2011-03-30 06:00:45 +0000
3453+++ mixxx/src/dlgprefvinyl.cpp 2011-04-16 23:24:33 +0000
3454@@ -31,7 +31,7 @@
3455
3456 DlgPrefVinyl::DlgPrefVinyl(QWidget * parent, SoundManager * soundman,
3457 ConfigObject<ConfigValue> * _config) : QWidget(parent), Ui::DlgPrefVinylDlg(),
3458- m_COTMode(ControlObject::getControl(ConfigKey("[VinylControl]", "Mode")))
3459+ m_COTMode(ControlObject::getControl(ConfigKey("[VinylControl]", "mode")))
3460 {
3461 m_pSoundManager = soundman;
3462 config = _config;
3463@@ -43,29 +43,9 @@
3464 QButtonGroup vinylControlMode;
3465 vinylControlMode.addButton(AbsoluteMode);
3466 vinylControlMode.addButton(RelativeMode);
3467- vinylControlMode.addButton(ScratchMode);
3468-
3469- //Get access to the timecode strength ControlObjects
3470- m_timecodeQuality1 = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey("[Channel1]", "VinylControlQuality")));
3471- m_timecodeQuality2 = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey("[Channel2]", "VinylControlQuality")));
3472-
3473- m_vinylControlInput1L = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey("[Channel1]", "VinylControlInputL")));
3474- m_vinylControlInput1R = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey("[Channel1]", "VinylControlInputR")));
3475- m_vinylControlInput2L = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey("[Channel2]", "VinylControlInputL")));
3476- m_vinylControlInput2R = new ControlObjectThreadMain(ControlObject::getControl(ConfigKey("[Channel2]", "VinylControlInputR")));
3477-
3478-
3479-
3480- m_signalWidget1.setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
3481- m_signalWidget2.setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
3482- const unsigned signalWidgetWidth = 75;
3483- const unsigned signalWidgetHeight = 75;
3484- m_signalWidget1.setMinimumSize(signalWidgetWidth, signalWidgetHeight);
3485- m_signalWidget2.setMinimumSize(signalWidgetWidth, signalWidgetHeight);
3486- m_signalWidget1.setMaximumSize(signalWidgetWidth, signalWidgetHeight);
3487- m_signalWidget2.setMaximumSize(signalWidgetWidth, signalWidgetHeight);
3488- m_signalWidget1.setupWidget();
3489- m_signalWidget2.setupWidget();
3490+
3491+ m_signalWidget1.setSize(MIXXX_VINYL_SCOPE_SIZE);
3492+ m_signalWidget2.setSize(MIXXX_VINYL_SCOPE_SIZE);
3493
3494 delete groupBoxSignalQuality->layout();
3495 QHBoxLayout *layout = new QHBoxLayout;
3496@@ -74,22 +54,38 @@
3497 groupBoxSignalQuality->setLayout(layout);
3498
3499 // Add vinyl types
3500- ComboBoxVinylType->addItem(MIXXX_VINYL_SERATOCV02VINYLSIDEA);
3501- ComboBoxVinylType->addItem(MIXXX_VINYL_SERATOCV02VINYLSIDEB);
3502- ComboBoxVinylType->addItem(MIXXX_VINYL_SERATOCD);
3503- ComboBoxVinylType->addItem(MIXXX_VINYL_TRAKTORSCRATCHSIDEA);
3504- ComboBoxVinylType->addItem(MIXXX_VINYL_TRAKTORSCRATCHSIDEB);
3505-
3506+ ComboBoxVinylType1->addItem(MIXXX_VINYL_SERATOCV02VINYLSIDEA);
3507+ ComboBoxVinylType1->addItem(MIXXX_VINYL_SERATOCV02VINYLSIDEB);
3508+ ComboBoxVinylType1->addItem(MIXXX_VINYL_SERATOCD);
3509+ ComboBoxVinylType1->addItem(MIXXX_VINYL_TRAKTORSCRATCHSIDEA);
3510+ ComboBoxVinylType1->addItem(MIXXX_VINYL_TRAKTORSCRATCHSIDEB);
3511+
3512+ ComboBoxVinylType2->addItem(MIXXX_VINYL_SERATOCV02VINYLSIDEA);
3513+ ComboBoxVinylType2->addItem(MIXXX_VINYL_SERATOCV02VINYLSIDEB);
3514+ ComboBoxVinylType2->addItem(MIXXX_VINYL_SERATOCD);
3515+ ComboBoxVinylType2->addItem(MIXXX_VINYL_TRAKTORSCRATCHSIDEA);
3516+ ComboBoxVinylType2->addItem(MIXXX_VINYL_TRAKTORSCRATCHSIDEB);
3517+
3518+ ComboBoxVinylSpeed1->addItem(MIXXX_VINYL_SPEED_33);
3519+ ComboBoxVinylSpeed1->addItem(MIXXX_VINYL_SPEED_45);
3520+
3521+ ComboBoxVinylSpeed2->addItem(MIXXX_VINYL_SPEED_33);
3522+ ComboBoxVinylSpeed2->addItem(MIXXX_VINYL_SPEED_45);
3523+
3524+ connect(applyButton, SIGNAL(clicked()), this, SLOT(slotApply()));
3525 connect(VinylGain, SIGNAL(sliderReleased()), this, SLOT(VinylGainSlotApply()));
3526 //connect(ComboBoxDeviceDeck1, SIGNAL(currentIndexChanged()), this, SLOT(()));
3527
3528 connect(VinylGain, SIGNAL(sliderReleased()), this, SLOT(settingsChanged()));
3529- connect(ComboBoxVinylType, SIGNAL(currentIndexChanged(int)), this, SLOT(settingsChanged()));
3530+ connect(ComboBoxVinylType1, SIGNAL(currentIndexChanged(int)), this, SLOT(settingsChanged()));
3531+ connect(ComboBoxVinylType2, SIGNAL(currentIndexChanged(int)), this, SLOT(settingsChanged()));
3532+ connect(ComboBoxVinylSpeed1, SIGNAL(currentIndexChanged(int)), this, SLOT(settingsChanged()));
3533+ connect(ComboBoxVinylSpeed2, SIGNAL(currentIndexChanged(int)), this, SLOT(settingsChanged()));
3534 connect(LeadinTime, SIGNAL(textChanged(const QString&)), this, SLOT(settingsChanged()));
3535 connect(NeedleSkipEnable, SIGNAL(stateChanged(int)), this, SLOT(settingsChanged()));
3536 connect(AbsoluteMode, SIGNAL(toggled(bool)), this, SLOT(settingsChanged()));
3537 connect(RelativeMode, SIGNAL(toggled(bool)), this, SLOT(settingsChanged()));
3538- connect(ScratchMode, SIGNAL(toggled(bool)), this, SLOT(settingsChanged()));
3539+ connect(m_pSoundManager, SIGNAL(devicesSetup()), this, SLOT(settingsChanged()));
3540 }
3541
3542 DlgPrefVinyl::~DlgPrefVinyl()
3543@@ -99,14 +95,11 @@
3544 /** @brief Performs any necessary actions that need to happen when the prefs dialog is opened */
3545 void DlgPrefVinyl::slotShow()
3546 {
3547- //Connect the signal quality ControlObjects to this dialog, so they start updating
3548- connect(m_timecodeQuality1, SIGNAL(valueChanged(double)), this, SLOT(updateSignalQuality1(double)));
3549- connect(m_timecodeQuality2, SIGNAL(valueChanged(double)), this, SLOT(updateSignalQuality2(double)));
3550-
3551- connect(m_vinylControlInput1L, SIGNAL(valueChanged(double)), this, SLOT(updateInputLevelLeft1(double)));
3552- connect(m_vinylControlInput1R, SIGNAL(valueChanged(double)), this, SLOT(updateInputLevelRight1(double)));
3553- connect(m_vinylControlInput2L, SIGNAL(valueChanged(double)), this, SLOT(updateInputLevelLeft2(double)));
3554- connect(m_vinylControlInput2R, SIGNAL(valueChanged(double)), this, SLOT(updateInputLevelRight2(double)));
3555+ QList<VinylControlProxy*> VCProxiesList = m_pSoundManager->getVinylControlProxies();
3556+ if (VCProxiesList.value(0) != NULL)
3557+ m_signalWidget1.setVinylControlProxy(VCProxiesList.value(0));
3558+ if (VCProxiesList.value(1) != NULL)
3559+ m_signalWidget2.setVinylControlProxy(VCProxiesList.value(1));
3560
3561 //(Re)Initialize the signal quality indicators
3562 m_signalWidget1.resetWidget();
3563@@ -122,12 +115,6 @@
3564 //Stop updating the vinyl control signal indicators when the prefs dialog is closed.
3565 m_signalWidget1.stopDrawing();
3566 m_signalWidget2.stopDrawing();
3567- m_timecodeQuality1->disconnect(this);
3568- m_timecodeQuality2->disconnect(this);
3569- m_vinylControlInput1L->disconnect(this);
3570- m_vinylControlInput1R->disconnect(this);
3571- m_vinylControlInput2L->disconnect(this);
3572- m_vinylControlInput2R->disconnect(this);
3573 }
3574
3575 void DlgPrefVinyl::slotUpdate()
3576@@ -135,33 +122,44 @@
3577 m_dontForce = true; // otherwise all the signals fired in here will cause
3578 // DlgPrefSound to call setupDevices needlessly :) -- bkgood
3579 // Set vinyl control types in the comboboxes
3580- int combo_index = ComboBoxVinylType->findText(config->getValueString(ConfigKey("[VinylControl]","strVinylType")));
3581- if (combo_index != -1)
3582- ComboBoxVinylType->setCurrentIndex(combo_index);
3583+ int combo_index = ComboBoxVinylType1->findText(config->getValueString(ConfigKey("[Channel1]","vinylcontrol_vinyl_type")));
3584+ if (combo_index != -1)
3585+ ComboBoxVinylType1->setCurrentIndex(combo_index);
3586+
3587+ combo_index = ComboBoxVinylType2->findText(config->getValueString(ConfigKey("[Channel2]","vinylcontrol_vinyl_type")));
3588+ if (combo_index != -1)
3589+ ComboBoxVinylType2->setCurrentIndex(combo_index);
3590+
3591+ combo_index = ComboBoxVinylSpeed1->findText(config->getValueString(ConfigKey("[Channel1]","vinylcontrol_speed_type")));
3592+ if (combo_index != -1)
3593+ ComboBoxVinylSpeed1->setCurrentIndex(combo_index);
3594+
3595+ combo_index = ComboBoxVinylSpeed2->findText(config->getValueString(ConfigKey("[Channel2]","vinylcontrol_speed_type")));
3596+ if (combo_index != -1)
3597+ ComboBoxVinylSpeed2->setCurrentIndex(combo_index);
3598
3599 // set lead-in time
3600- LeadinTime->setText (config->getValueString(ConfigKey("[VinylControl]","LeadInTime")) );
3601+ LeadinTime->setText (config->getValueString(ConfigKey("[VinylControl]","lead_in_time")) );
3602
3603 // set Relative mode
3604- int iMode = config->getValueString(ConfigKey("[VinylControl]","Mode")).toInt();
3605+ int iMode = config->getValueString(ConfigKey("[VinylControl]","mode")).toInt();
3606 if (iMode == MIXXX_VCMODE_ABSOLUTE)
3607 AbsoluteMode->setChecked(true);
3608 else if (iMode == MIXXX_VCMODE_RELATIVE)
3609 RelativeMode->setChecked(true);
3610- else if (iMode == MIXXX_VCMODE_SCRATCH)
3611- ScratchMode->setChecked(true);
3612
3613 // Honour the Needle Skip Prevention setting.
3614- NeedleSkipEnable->setChecked( (bool)config->getValueString( ConfigKey("[VinylControl]", "NeedleSkipPrevention") ).toInt() );
3615+ NeedleSkipEnable->setChecked( (bool)config->getValueString( ConfigKey("[VinylControl]", "needle_skip_prevention") ).toInt() );
3616
3617 //set vinyl control gain
3618- VinylGain->setValue( config->getValueString(ConfigKey("[VinylControl]","VinylControlGain")).toInt());
3619+ VinylGain->setValue( config->getValueString(ConfigKey("[VinylControl]","gain")).toInt());
3620 m_dontForce = false;
3621 }
3622
3623 // Update the config object with parameters from dialog
3624 void DlgPrefVinyl::slotApply()
3625 {
3626+ QString device2;
3627 qDebug() << "DlgPrefVinyl::Apply";
3628
3629 // Lead-in time
3630@@ -169,9 +167,9 @@
3631 bool isInteger;
3632 strLeadIn.toInt(&isInteger);
3633 if (isInteger)
3634- config->set(ConfigKey("[VinylControl]","LeadInTime"), strLeadIn);
3635+ config->set(ConfigKey("[VinylControl]","lead_in_time"), strLeadIn);
3636 else
3637- config->set(ConfigKey("[VinylControl]","LeadInTime"), MIXXX_VC_DEFAULT_LEADINTIME);
3638+ config->set(ConfigKey("[VinylControl]","lead_in_time"), MIXXX_VC_DEFAULT_LEADINTIME);
3639
3640 //Apply updates for everything else...
3641 VinylTypeSlotApply();
3642@@ -182,12 +180,12 @@
3643 iMode = MIXXX_VCMODE_ABSOLUTE;
3644 if (RelativeMode->isChecked())
3645 iMode = MIXXX_VCMODE_RELATIVE;
3646- if (ScratchMode->isChecked())
3647- iMode = MIXXX_VCMODE_SCRATCH;
3648
3649+ ControlObject::getControl(ConfigKey("[Channel1]", "vinylcontrol_mode"))->set(iMode);
3650+ ControlObject::getControl(ConfigKey("[Channel2]", "vinylcontrol_mode"))->set(iMode);
3651 m_COTMode.slotSet(iMode);
3652- config->set(ConfigKey("[VinylControl]","Mode"), ConfigValue(iMode));
3653- config->set(ConfigKey("[VinylControl]","NeedleSkipPrevention" ), ConfigValue( (int)(NeedleSkipEnable->isChecked( )) ) );
3654+ config->set(ConfigKey("[VinylControl]","mode"), ConfigValue(iMode));
3655+ config->set(ConfigKey("[VinylControl]","needle_skip_prevention" ), ConfigValue( (int)(NeedleSkipEnable->isChecked( )) ) );
3656
3657 slotUpdate();
3658 }
3659@@ -197,80 +195,39 @@
3660
3661 }
3662
3663-void DlgPrefVinyl::EnableScratchModeSlotApply()
3664-{
3665-
3666-}
3667-
3668 void DlgPrefVinyl::VinylTypeSlotApply()
3669 {
3670- config->set(ConfigKey("[VinylControl]","strVinylType"), ConfigValue(ComboBoxVinylType->currentText()));
3671+ config->set(ConfigKey("[Channel1]","vinylcontrol_vinyl_type"), ConfigValue(ComboBoxVinylType1->currentText()));
3672+ config->set(ConfigKey("[Channel2]","vinylcontrol_vinyl_type"), ConfigValue(ComboBoxVinylType2->currentText()));
3673+ config->set(ConfigKey("[Channel1]","vinylcontrol_speed_type"), ConfigValue(ComboBoxVinylSpeed1->currentText()));
3674+ config->set(ConfigKey("[Channel2]","vinylcontrol_speed_type"), ConfigValue(ComboBoxVinylSpeed2->currentText()));
3675+ emit(applySound());
3676 }
3677
3678 void DlgPrefVinyl::VinylGainSlotApply()
3679 {
3680 qDebug() << "in VinylGainSlotApply()" << "with gain:" << VinylGain->value();
3681 //Update the config key...
3682- config->set(ConfigKey("[VinylControl]","VinylControlGain"), ConfigValue(VinylGain->value()));
3683+ config->set(ConfigKey("[VinylControl]","gain"), ConfigValue(VinylGain->value()));
3684
3685 //Update the ControlObject...
3686- ControlObject* pControlObjectVinylControlGain = ControlObject::getControl(ConfigKey("[VinylControl]", "VinylControlGain"));
3687+ ControlObject* pControlObjectVinylControlGain = ControlObject::getControl(ConfigKey("[VinylControl]", "gain"));
3688 pControlObjectVinylControlGain->set(VinylGain->value());
3689-
3690- //qDebug() << "Setting Gain Text";
3691- //gain->setText(config->getValueString(ConfigKey("[VinylControl]","VinylControlGain"))); //this is probably ineffecient...
3692-}
3693-
3694-/** @brief Wraps updateSignalQuality to work nicely with slots
3695- * @param value The new signal quality level for channel 1 (0.0f-1.0f)
3696- */
3697-void DlgPrefVinyl::updateSignalQuality1(double value)
3698-{
3699- m_signalWidget1.updateSignalQuality(VINYLCONTROL_SIGQUALITY, value);
3700-}
3701-
3702-/** @brief Wraps updateSignalQuality to work nicely with slots
3703- * @param value The new signal quality level for channel 2 (0.0f-1.0f)
3704- */
3705-void DlgPrefVinyl::updateSignalQuality2(double value)
3706-{
3707- m_signalWidget2.updateSignalQuality(VINYLCONTROL_SIGQUALITY, value);
3708-}
3709-
3710-/** @brief Wraps updateSignalQuality to work nicely with slots
3711- * @param value The new input level for the left channel of the first deck (0.0f-1.0f)
3712- */
3713-void DlgPrefVinyl::updateInputLevelLeft1(double value)
3714-{
3715- m_signalWidget1.updateSignalQuality(VINYLCONTROL_SIGLEFTCHANNEL, value);
3716-}
3717-
3718-/** @brief Wraps updateSignalQuality to work nicely with slots
3719- * @param value The new input level for the right channel of the first deck (0.0f-1.0f)
3720- */
3721-void DlgPrefVinyl::updateInputLevelRight1(double value)
3722-{
3723- m_signalWidget1.updateSignalQuality(VINYLCONTROL_SIGRIGHTCHANNEL, value);
3724-}
3725-
3726-/** @brief Wraps updateSignalQuality to work nicely with slots
3727- * @param value The new input level for the left channel of the second deck (0.0f-1.0f)
3728- */
3729-void DlgPrefVinyl::updateInputLevelLeft2(double value)
3730-{
3731- m_signalWidget2.updateSignalQuality(VINYLCONTROL_SIGLEFTCHANNEL, value);
3732-}
3733-
3734-/** @brief Wraps updateSignalQuality to work nicely with slots
3735- * @param value The new input level for the right channel of the second deck (0.0f-1.0f)
3736- */
3737-void DlgPrefVinyl::updateInputLevelRight2(double value)
3738-{
3739- m_signalWidget2.updateSignalQuality(VINYLCONTROL_SIGRIGHTCHANNEL, value);
3740 }
3741
3742 void DlgPrefVinyl::settingsChanged() {
3743 if (!m_dontForce) {
3744 emit(refreshVCProxies());
3745 }
3746+
3747+ QList<VinylControlProxy*> VCProxiesList = m_pSoundManager->getVinylControlProxies();
3748+ if (VCProxiesList.value(0) != NULL) {
3749+ m_signalWidget1.setVinylControlProxy(VCProxiesList.value(0));
3750+ }
3751+ if (VCProxiesList.value(1) != NULL) {
3752+ m_signalWidget2.setVinylControlProxy(VCProxiesList.value(1));
3753+ }
3754+
3755+ m_signalWidget1.setVinylActive(m_pSoundManager->hasVinylInput(0));
3756+ m_signalWidget2.setVinylActive(m_pSoundManager->hasVinylInput(1));
3757 }
3758
3759=== modified file 'mixxx/src/dlgprefvinyl.h'
3760--- mixxx/src/dlgprefvinyl.h 2011-03-30 06:00:45 +0000
3761+++ mixxx/src/dlgprefvinyl.h 2011-04-16 23:24:33 +0000
3762@@ -44,22 +44,16 @@
3763 /** Update widget */
3764 void slotUpdate();
3765 void slotApply();
3766- void EnableRelativeModeSlotApply();
3767- void EnableScratchModeSlotApply();
3768- void VinylTypeSlotApply();
3769+ void EnableRelativeModeSlotApply();
3770+ void VinylTypeSlotApply();
3771 void VinylGainSlotApply();
3772- void updateSignalQuality1(double value);
3773- void updateSignalQuality2(double value);
3774- void updateInputLevelLeft1(double value);
3775- void updateInputLevelRight1(double value);
3776- void updateInputLevelLeft2(double value);
3777- void updateInputLevelRight2(double value);
3778 void slotClose();
3779 void slotShow();
3780
3781 signals:
3782 void apply();
3783 void refreshVCProxies();
3784+ void applySound();
3785 private slots:
3786 void settingsChanged();
3787 private:
3788
3789=== modified file 'mixxx/src/dlgprefvinyldlg.ui'
3790--- mixxx/src/dlgprefvinyldlg.ui 2010-11-29 21:23:11 +0000
3791+++ mixxx/src/dlgprefvinyldlg.ui 2011-04-16 23:24:33 +0000
3792@@ -14,7 +14,7 @@
3793 <string>Form1</string>
3794 </property>
3795 <layout class="QGridLayout" name="gridLayout_3">
3796- <item row="0" column="0" colspan="2">
3797+ <item row="0" column="0" colspan="3">
3798 <widget class="QGroupBox" name="groupBox_2">
3799 <property name="title">
3800 <string>Input</string>
3801@@ -128,21 +128,16 @@
3802 </layout>
3803 </item>
3804 </layout>
3805- <zorder>devicesLabel</zorder>
3806- <zorder>label_2</zorder>
3807- <zorder>horizontalLayoutWidget</zorder>
3808- <zorder>textLabel1_3</zorder>
3809- <zorder>horizontalLayoutWidget_2</zorder>
3810 </widget>
3811 </item>
3812- <item row="1" column="0" colspan="2">
3813+ <item row="1" column="0" colspan="3">
3814 <widget class="QGroupBox" name="groupBox_3">
3815 <property name="title">
3816 <string>Vinyl Configuration</string>
3817 </property>
3818 <layout class="QGridLayout" name="gridLayout_2">
3819 <item row="0" column="0">
3820- <widget class="QLabel" name="TextLabel2">
3821+ <widget class="QLabel" name="TextLabel29">
3822 <property name="enabled">
3823 <bool>true</bool>
3824 </property>
3825@@ -156,15 +151,31 @@
3826 <font/>
3827 </property>
3828 <property name="text">
3829- <string>Vinyl Type</string>
3830+ <string>Deck 1 Vinyl Type</string>
3831 </property>
3832 <property name="wordWrap">
3833 <bool>false</bool>
3834 </property>
3835 </widget>
3836 </item>
3837- <item row="0" column="2" colspan="2">
3838- <widget class="QComboBox" name="ComboBoxVinylType">
3839+ <item row="0" column="2">
3840+ <widget class="QComboBox" name="ComboBoxVinylType1">
3841+ <property name="enabled">
3842+ <bool>true</bool>
3843+ </property>
3844+ <property name="sizePolicy">
3845+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
3846+ <horstretch>0</horstretch>
3847+ <verstretch>0</verstretch>
3848+ </sizepolicy>
3849+ </property>
3850+ <property name="font">
3851+ <font/>
3852+ </property>
3853+ </widget>
3854+ </item>
3855+ <item row="0" column="3">
3856+ <widget class="QComboBox" name="ComboBoxVinylSpeed1">
3857 <property name="enabled">
3858 <bool>true</bool>
3859 </property>
3860@@ -180,6 +191,60 @@
3861 </widget>
3862 </item>
3863 <item row="1" column="0">
3864+ <widget class="QLabel" name="TextLabel28">
3865+ <property name="enabled">
3866+ <bool>true</bool>
3867+ </property>
3868+ <property name="sizePolicy">
3869+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
3870+ <horstretch>0</horstretch>
3871+ <verstretch>0</verstretch>
3872+ </sizepolicy>
3873+ </property>
3874+ <property name="font">
3875+ <font/>
3876+ </property>
3877+ <property name="text">
3878+ <string>Deck 2 Vinyl Type</string>
3879+ </property>
3880+ <property name="wordWrap">
3881+ <bool>false</bool>
3882+ </property>
3883+ </widget>
3884+ </item>
3885+ <item row="1" column="2">
3886+ <widget class="QComboBox" name="ComboBoxVinylType2">
3887+ <property name="enabled">
3888+ <bool>true</bool>
3889+ </property>
3890+ <property name="sizePolicy">
3891+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
3892+ <horstretch>0</horstretch>
3893+ <verstretch>0</verstretch>
3894+ </sizepolicy>
3895+ </property>
3896+ <property name="font">
3897+ <font/>
3898+ </property>
3899+ </widget>
3900+ </item>
3901+ <item row="1" column="3">
3902+ <widget class="QComboBox" name="ComboBoxVinylSpeed2">
3903+ <property name="enabled">
3904+ <bool>true</bool>
3905+ </property>
3906+ <property name="sizePolicy">
3907+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
3908+ <horstretch>0</horstretch>
3909+ <verstretch>0</verstretch>
3910+ </sizepolicy>
3911+ </property>
3912+ <property name="font">
3913+ <font/>
3914+ </property>
3915+ </widget>
3916+ </item>
3917+ <item row="2" column="0">
3918 <widget class="QLabel" name="TextLabel2_2">
3919 <property name="enabled">
3920 <bool>true</bool>
3921@@ -201,7 +266,7 @@
3922 </property>
3923 </widget>
3924 </item>
3925- <item row="1" column="2">
3926+ <item row="2" column="2">
3927 <widget class="QLineEdit" name="LeadinTime">
3928 <property name="sizePolicy">
3929 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
3930@@ -211,7 +276,7 @@
3931 </property>
3932 </widget>
3933 </item>
3934- <item row="1" column="3">
3935+ <item row="2" column="3">
3936 <widget class="QLabel" name="TextLabel2_2_2">
3937 <property name="enabled">
3938 <bool>true</bool>
3939@@ -227,7 +292,7 @@
3940 </property>
3941 </widget>
3942 </item>
3943- <item row="1" column="1">
3944+ <item row="3" column="1">
3945 <spacer name="horizontalSpacer_2">
3946 <property name="orientation">
3947 <enum>Qt::Horizontal</enum>
3948@@ -243,17 +308,26 @@
3949 </layout>
3950 </widget>
3951 </item>
3952- <item row="2" column="0" rowspan="2">
3953+ <item row="2" column="0" rowspan="3">
3954 <widget class="QGroupBox" name="groupBox">
3955 <property name="sizePolicy">
3956- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
3957+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
3958 <horstretch>0</horstretch>
3959 <verstretch>0</verstretch>
3960 </sizepolicy>
3961 </property>
3962+ <property name="maximumSize">
3963+ <size>
3964+ <width>16777215</width>
3965+ <height>16777215</height>
3966+ </size>
3967+ </property>
3968 <property name="title">
3969 <string>Control Mode</string>
3970 </property>
3971+ <property name="alignment">
3972+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
3973+ </property>
3974 <layout class="QVBoxLayout">
3975 <item>
3976 <widget class="QRadioButton" name="AbsoluteMode">
3977@@ -270,27 +344,36 @@
3978 </widget>
3979 </item>
3980 <item>
3981- <widget class="QRadioButton" name="ScratchMode">
3982- <property name="text">
3983- <string>Scratch Mode</string>
3984- </property>
3985- </widget>
3986- </item>
3987- <item>
3988 <widget class="QCheckBox" name="NeedleSkipEnable">
3989 <property name="text">
3990 <string>Enable Needle Skip Prevention</string>
3991 </property>
3992 </widget>
3993 </item>
3994+ <item>
3995+ <spacer name="verticalSpacer_3">
3996+ <property name="orientation">
3997+ <enum>Qt::Vertical</enum>
3998+ </property>
3999+ <property name="sizeHint" stdset="0">
4000+ <size>
4001+ <width>20</width>
4002+ <height>40</height>
4003+ </size>
4004+ </property>
4005+ </spacer>
4006+ </item>
4007 </layout>
4008 </widget>
4009 </item>
4010- <item row="2" column="1">
4011+ <item row="2" column="1" colspan="2">
4012 <widget class="QGroupBox" name="groupBoxSignalQuality">
4013 <property name="title">
4014 <string>Signal Quality</string>
4015 </property>
4016+ <property name="alignment">
4017+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
4018+ </property>
4019 <layout class="QGridLayout">
4020 <item row="0" column="0">
4021 <spacer name="verticalSpacer_2">
4022@@ -308,7 +391,7 @@
4023 </layout>
4024 </widget>
4025 </item>
4026- <item row="3" column="1">
4027+ <item row="3" column="1" colspan="2">
4028 <widget class="QLabel" name="label">
4029 <property name="enabled">
4030 <bool>false</bool>
4031@@ -324,15 +407,34 @@
4032 </property>
4033 </widget>
4034 </item>
4035- <item row="4" column="1">
4036+ <item row="4" column="2">
4037+ <widget class="QPushButton" name="applyButton">
4038+ <property name="sizePolicy">
4039+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
4040+ <horstretch>0</horstretch>
4041+ <verstretch>0</verstretch>
4042+ </sizepolicy>
4043+ </property>
4044+ <property name="sizeHint" stdset="0">
4045+ <size>
4046+ <width>85</width>
4047+ <height>27</height>
4048+ </size>
4049+ </property>
4050+ <property name="text">
4051+ <string>Apply</string>
4052+ </property>
4053+ </widget>
4054+ </item>
4055+ <item row="5" column="2">
4056 <spacer name="verticalSpacer">
4057 <property name="orientation">
4058 <enum>Qt::Vertical</enum>
4059 </property>
4060 <property name="sizeHint" stdset="0">
4061 <size>
4062- <width>51</width>
4063- <height>59</height>
4064+ <width>0</width>
4065+ <height>0</height>
4066 </size>
4067 </property>
4068 </spacer>
4069
4070=== modified file 'mixxx/src/engine/bpmcontrol.cpp'
4071--- mixxx/src/engine/bpmcontrol.cpp 2011-04-03 20:51:04 +0000
4072+++ mixxx/src/engine/bpmcontrol.cpp 2011-04-16 23:24:33 +0000
4073@@ -78,7 +78,7 @@
4074 }
4075
4076 void BpmControl::slotFileBpmChanged(double bpm) {
4077- qDebug() << this << "slotFileBpmChanged" << bpm;
4078+ //qDebug() << this << "slotFileBpmChanged" << bpm;
4079 // Adjust the file-bpm with the current setting of the rate to get the
4080 // engine BPM.
4081 double dRate = 1.0 + m_pRateDir->get() * m_pRateRange->get() * m_pRateSlider->get();
4082
4083=== modified file 'mixxx/src/engine/enginebuffer.cpp'
4084--- mixxx/src/engine/enginebuffer.cpp 2011-04-07 05:08:22 +0000
4085+++ mixxx/src/engine/enginebuffer.cpp 2011-04-16 23:24:33 +0000
4086@@ -39,6 +39,11 @@
4087 #include "engine/quantizecontrol.h"
4088
4089
4090+#ifdef __VINYLCONTROL__
4091+#include "vinylcontrol.h"
4092+#include "library/dao/cue.h"
4093+#endif
4094+
4095 #include "trackinfoobject.h"
4096
4097 #ifdef _MSC_VER
4098@@ -72,8 +77,11 @@
4099 m_pScaleLinear(NULL),
4100 m_pScaleST(NULL),
4101 m_bScalerChanged(false),
4102- m_fLastSampleValue(0.),
4103- m_bLastBufferPaused(true) {
4104+ m_bLastBufferPaused(true),
4105+ m_fRampValue(0.0) {
4106+
4107+ m_fLastSampleValue[0] = 0;
4108+ m_fLastSampleValue[1] = 0;
4109
4110 m_pReader = new CachingReader(_group, _config);
4111 connect(m_pReader, SIGNAL(trackLoaded(TrackPointer, int, int)),
4112@@ -83,7 +91,6 @@
4113 this, SLOT(slotTrackLoadFailed(TrackPointer, QString)),
4114 Qt::DirectConnection);
4115
4116-
4117 // Play button
4118 playButton = new ControlPushButton(ConfigKey(group, "play"));
4119 playButton->setToggleButton(true);
4120@@ -126,14 +133,15 @@
4121 rateEngine = new ControlObject(ConfigKey(group, "rateEngine"));
4122
4123 // Slider to show and change song position
4124- playposSlider = new ControlPotmeter(ConfigKey(group, "playposition"), 0., 1.);
4125+ //these bizarre choices map conveniently to the 0-127 range of midi
4126+ playposSlider = new ControlPotmeter(ConfigKey(group, "playposition"), -0.14, 1.14);
4127 connect(playposSlider, SIGNAL(valueChanged(double)),
4128 this, SLOT(slotControlSeek(double)),
4129 Qt::DirectConnection);
4130
4131 // Control used to communicate ratio playpos to GUI thread
4132 visualPlaypos =
4133- new ControlPotmeter(ConfigKey(group, "visual_playposition"), 0., 1.);
4134+ new ControlPotmeter(ConfigKey(group, "visual_playposition"), -0.14, 1.14);
4135
4136 // m_pTrackEnd is used to signal when at end of file during
4137 // playback. TODO(XXX) This should not even be a control object because it
4138@@ -145,6 +153,14 @@
4139 m_pRepeat = new ControlPushButton(ConfigKey(group, "repeat"));
4140 m_pRepeat->setToggleButton(true);
4141
4142+#ifdef __VINYLCONTROL__
4143+ m_pVinylStatus = new ControlObject(ConfigKey(group,"vinylcontrol_status"));
4144+ m_pVinylSeek = new ControlObject(ConfigKey(group,"vinylcontrol_seek"));
4145+ connect(m_pVinylSeek, SIGNAL(valueChanged(double)),
4146+ this, SLOT(slotControlVinylSeek(double)),
4147+ Qt::DirectConnection);
4148+#endif
4149+
4150 // Sample rate
4151 m_pSampleRate = ControlObject::getControl(ConfigKey("[Master]","samplerate"));
4152
4153@@ -192,10 +208,17 @@
4154 this, SLOT(slotEjectTrack(double)),
4155 Qt::DirectConnection);
4156
4157+ //m_iRampIter = 0;
4158+
4159+ /*df.setFileName("mixxx-debug.csv");
4160+ df.open(QIODevice::WriteOnly | QIODevice::Text);
4161+ writer.setDevice(&df);*/
4162 }
4163
4164 EngineBuffer::~EngineBuffer()
4165 {
4166+ //close the writer
4167+ /*df.close();*/
4168 delete m_pReadAheadManager;
4169 delete m_pReader;
4170
4171@@ -353,21 +376,85 @@
4172 emit(trackUnloaded(pTrack));
4173 }
4174
4175+void EngineBuffer::slotControlVinylSeek(double change)
4176+{
4177+#ifdef __VINYLCONTROL__
4178+ if(isnan(change) || change > 1.14 || change < -1.14) {
4179+ // This seek is ridiculous.
4180+ return;
4181+ }
4182+
4183+ double new_playpos = round(change*file_length_old);
4184+
4185+ ControlObject *pVinylMode = ControlObject::getControl(ConfigKey(group,"vinylcontrol_mode"));
4186+ ControlObject *pVinylEnabled = ControlObject::getControl(ConfigKey(group,"vinylcontrol_enabled"));
4187+
4188+ if (m_pCurrentTrack != NULL && pVinylEnabled != NULL && pVinylMode != NULL) {
4189+ if (pVinylEnabled->get() && pVinylMode->get() == MIXXX_VCMODE_RELATIVE) {
4190+ int cuemode = (int)ControlObject::getControl(ConfigKey(group,"vinylcontrol_cueing"))->get();
4191+
4192+ //if in preroll, always seek
4193+ if (new_playpos < 0) {
4194+ slotControlSeek(change);
4195+ return;
4196+ } else if (cuemode == MIXXX_RELATIVE_CUE_OFF) {
4197+ return; //if off, do nothing
4198+ } else if (cuemode == MIXXX_RELATIVE_CUE_ONECUE) {
4199+ //if onecue, just seek to the regular cue
4200+ slotControlSeekAbs(m_pCurrentTrack->getCuePoint());
4201+ return;
4202+ }
4203+
4204+ double distance = 0;
4205+ int nearest_playpos = -1;
4206+
4207+ QList<Cue*> cuePoints = m_pCurrentTrack->getCuePoints();
4208+ QListIterator<Cue*> it(cuePoints);
4209+ while (it.hasNext()) {
4210+ Cue* pCue = it.next();
4211+ if (pCue->getType() != Cue::CUE || pCue->getHotCue() == -1)
4212+ continue;
4213+
4214+ int cue_position = pCue->getPosition();
4215+ //pick cues closest to new_playpos
4216+ if ((nearest_playpos == -1) ||
4217+ (fabs(new_playpos - cue_position) < distance)) {
4218+ nearest_playpos = cue_position;
4219+ distance = fabs(new_playpos - cue_position);
4220+ }
4221+ }
4222+
4223+ if (nearest_playpos == -1) {
4224+ if (new_playpos >= 0)
4225+ //never found an appropriate cue, so don't seek?
4226+ return;
4227+ //if negative, allow a seek by falling down to the bottom
4228+ } else {
4229+ slotControlSeekAbs((float)nearest_playpos);
4230+ return;
4231+ }
4232+ }
4233+ }
4234+ //just seek where it wanted to originally
4235+ slotControlSeek(change);
4236+#endif
4237+}
4238
4239 // WARNING: This method runs in both the GUI thread and the Engine Thread
4240 void EngineBuffer::slotControlSeek(double change)
4241 {
4242- if(isnan(change) || change > 1.0 || change < 0.0) {
4243+ if(isnan(change) || change > 1.14 || change < -1.14) {
4244 // This seek is ridiculous.
4245 return;
4246 }
4247
4248 // Find new playpos, restrict to valid ranges.
4249 double new_playpos = round(change*file_length_old);
4250+
4251 if (new_playpos > file_length_old)
4252 new_playpos = file_length_old;
4253- if (new_playpos < 0.)
4254- new_playpos = 0.;
4255+ //if (new_playpos < 0.)
4256+ // new_playpos = 0.;
4257
4258 // Ensure that the file position is even (remember, stereo channel files...)
4259 if (!even((int)new_playpos))
4260@@ -445,6 +532,7 @@
4261 CSAMPLE * pOutput = (CSAMPLE *)pOut;
4262
4263 bool bCurBufferPaused = false;
4264+ double rate;
4265
4266 if (!m_pTrackEnd->get() && pause.tryLock()) {
4267
4268@@ -462,19 +550,22 @@
4269
4270 bool paused = playButton->get() != 0.0f ? false : true;
4271
4272- double rate = m_pRateControl->calculateRate(baserate, paused);
4273+ rate = m_pRateControl->calculateRate(baserate, paused);
4274 //qDebug() << "rate" << rate << " paused" << paused;
4275
4276 // If the rate has changed, set it in the scale object
4277 if (rate != rate_old || m_bScalerChanged) {
4278 // The rate returned by the scale object can be different from the wanted rate!
4279-
4280- //XXX: Trying to force RAMAN to read from correct
4281- // playpos when rate changes direction - Albert
4282- if (m_bScalerChanged || (rate_old <= 0 && rate > 0) ||
4283- (rate_old >= 0 && rate < 0))
4284- {
4285+ // Make sure new scaler has proper position
4286+ if (m_bScalerChanged) {
4287 setNewPlaypos(filepos_play);
4288+ } else if (m_pScale != m_pScaleLinear) { //linear scaler does this part for us now
4289+ //XXX: Trying to force RAMAN to read from correct
4290+ // playpos when rate changes direction - Albert
4291+ if ((rate_old <= 0 && rate > 0) ||
4292+ (rate_old >= 0 && rate < 0)) {
4293+ setNewPlaypos(filepos_play);
4294+ }
4295 }
4296
4297 rate_old = rate;
4298@@ -493,9 +584,10 @@
4299 // If we're playing past the end, playing before the start, or standing
4300 // still then by definition the buffer is paused.
4301 bCurBufferPaused = rate == 0 ||
4302- (at_start && backwards) ||
4303+ //(at_start && backwards) ||
4304 (at_end && !backwards);
4305
4306+
4307 // If the buffer is not paused, then scale the audio.
4308 if (!bCurBufferPaused) {
4309 CSAMPLE *output;
4310@@ -531,9 +623,9 @@
4311
4312 // Adjust filepos_play by the amount we processed.
4313 filepos_play += idx;
4314- filepos_play = math_max(0, filepos_play);
4315- // We need the above protection against negative playpositions
4316- // in case SoundTouch/EngineBufferSoundTouch gives us too many samples.
4317+ //filepos_play = math_max(0, filepos_play);
4318+ //// We need the above protection against negative playpositions
4319+ //// in case SoundTouch/EngineBufferSoundTouch gives us too many samples.
4320
4321 // Get rid of annoying decimals that the scaler sometimes produces
4322 filepos_play = round(filepos_play);
4323@@ -599,7 +691,7 @@
4324
4325 bool repeat_enabled = m_pRepeat->get() != 0.0f;
4326
4327- bool end_of_track = (at_start && backwards) ||
4328+ bool end_of_track = //(at_start && backwards) ||
4329 (at_end && !backwards);
4330
4331 // If playbutton is pressed, check if we are at start or end of track
4332@@ -624,27 +716,57 @@
4333 // (hopefully) before the next callback.
4334 m_pReader->wake();
4335
4336- // Force ramp in if this is the first buffer during a play
4337 if (m_bLastBufferPaused && !bCurBufferPaused) {
4338- // Ramp from zero
4339- int iLen = math_min(iBufferSize, kiRampLength);
4340- float fStep = pOutput[iLen-1]/(float)iLen;
4341- for (int i=0; i<iLen; ++i)
4342- pOutput[i] = fStep*i;
4343-
4344- }
4345- // Force ramp out if this is the first buffer to be paused.
4346- else if (!m_bLastBufferPaused && bCurBufferPaused) {
4347- rampOut(pOut, iBufferSize);
4348- }
4349- // If the previous buffer was paused and we are currently paused, then
4350- // memset the output to zero.
4351- else if (m_bLastBufferPaused && bCurBufferPaused) {
4352- memset(pOutput, 0, sizeof(pOutput[0])*iBufferSize);
4353+ if (fabs(rate) > 0.005) //at very slow forward rates, don't ramp up
4354+ m_iRampState = ENGINE_RAMP_UP;
4355+ } else if (!m_bLastBufferPaused && bCurBufferPaused) {
4356+ m_iRampState = ENGINE_RAMP_DOWN;
4357+ } else { //we are not changing state
4358+ //make sure we aren't accidentally ramping down
4359+ //this is how we make sure that ramp value will become 1.0 eventually
4360+ if (fabs(rate) > 0.005 && m_iRampState != ENGINE_RAMP_UP && m_fRampValue < 1.0)
4361+ m_iRampState = ENGINE_RAMP_UP;
4362+ }
4363+
4364+ //let's try holding the last sample value constant, and pull it
4365+ //towards zero
4366+ float ramp_inc = 0;
4367+ if (m_iRampState == ENGINE_RAMP_UP) {
4368+ ramp_inc = (m_iRampState * 0.2) / iBufferSize; //ramp up quickly (5 frames)
4369+ } else if (m_iRampState == ENGINE_RAMP_DOWN) {
4370+ ramp_inc = (m_iRampState * 0.08) / iBufferSize; //but down slowly
4371+ }
4372+
4373+ //float fakerate = rate * 30000 == 0 ? -5000 : rate*30000;
4374+ for (int i=0; i<iBufferSize; i+=2) {
4375+ if (bCurBufferPaused) {
4376+ float dither = (float)(rand() % 32768) / 32768 - 0.5; // dither
4377+ pOutput[i] = m_fLastSampleValue[0] * m_fRampValue + dither;
4378+ pOutput[i+1] = m_fLastSampleValue[1] * m_fRampValue + dither;
4379+ } else {
4380+ pOutput[i] = pOutput[i] * m_fRampValue;
4381+ pOutput[i+1] = pOutput[i+1] * m_fRampValue;
4382+ }
4383+
4384+ //writer << pOutput[i] << "\n";
4385+ m_fRampValue += ramp_inc;
4386+ if (m_fRampValue >= 1.0) {
4387+ m_iRampState = ENGINE_RAMP_NONE;
4388+ m_fRampValue = 1.0;
4389+ }
4390+ if (m_fRampValue <= 0.0) {
4391+ m_iRampState = ENGINE_RAMP_NONE;
4392+ m_fRampValue = 0.0;
4393+ }
4394+ }
4395+
4396+ if ((!bCurBufferPaused && m_iRampState == ENGINE_RAMP_NONE) ||
4397+ (bCurBufferPaused && m_fRampValue == 0.0)) {
4398+ m_fLastSampleValue[0] = pOutput[iBufferSize-2];
4399+ m_fLastSampleValue[1] = pOutput[iBufferSize-1];
4400 }
4401
4402 m_bLastBufferPaused = bCurBufferPaused;
4403- m_fLastSampleValue = pOutput[iBufferSize-1];
4404 }
4405
4406
4407@@ -656,15 +778,24 @@
4408
4409 // Ramp to zero
4410 int i=0;
4411- if (m_fLastSampleValue!=0.)
4412- {
4413- int iLen = math_min(iBufferSize, kiRampLength);
4414- float fStep = m_fLastSampleValue/(float)iLen;
4415+ if (m_fLastSampleValue[0]!=0.) {
4416 // TODO(XXX) SSE
4417- while (i<iLen)
4418- {
4419- pOutput[i] = fStep*(iLen-(i+1));
4420- ++i;
4421+ if (pOutput[0] == 0) {
4422+ while (i<iBufferSize) {
4423+ float sigmoid = sigmoid_zero((float)(iBufferSize - i), (float)iBufferSize);
4424+ float dither = (float)(rand() % 32768) / 32768 - 0.5; // dither
4425+ pOutput[i] = (float)m_fLastSampleValue[0] * sigmoid + dither;
4426+ pOutput[i+1] = (float)m_fLastSampleValue[1] * sigmoid + dither;
4427+ i+=2;
4428+ }
4429+ } else {
4430+ while (i<iBufferSize) {
4431+ float sigmoid = sigmoid_zero((float)(iBufferSize - i), (float)iBufferSize);
4432+ float dither = (float)(rand() % 32768) / 32768 - 0.5; // dither
4433+ pOutput[i] = (float)pOutput[i] * sigmoid + dither;
4434+ pOutput[i+1] = (float)pOutput[i+1] * sigmoid + dither;
4435+ i+=2;
4436+ }
4437 }
4438 }
4439
4440@@ -684,7 +815,7 @@
4441
4442 double fFractionalPlaypos = 0.0;
4443 if (file_length_old!=0.) {
4444- fFractionalPlaypos = math_max(0.,math_min(filepos_play,file_length_old));
4445+ fFractionalPlaypos = math_min(filepos_play,file_length_old);
4446 fFractionalPlaypos /= file_length_old;
4447 } else {
4448 fFractionalPlaypos = 0.;
4449
4450=== modified file 'mixxx/src/engine/enginebuffer.h'
4451--- mixxx/src/engine/enginebuffer.h 2011-04-07 04:23:53 +0000
4452+++ mixxx/src/engine/enginebuffer.h 2011-04-16 23:24:33 +0000
4453@@ -25,6 +25,8 @@
4454 #include "trackinfoobject.h"
4455 #include "configobject.h"
4456 #include "rotary.h"
4457+//for the writer
4458+//#include <QtCore>
4459
4460 class EngineControl;
4461 class BpmControl;
4462@@ -64,10 +66,18 @@
4463 const int TRACK_END_MODE_LOOP = 2;
4464 const int TRACK_END_MODE_PING = 3;
4465
4466-// Maximum number of samples used to ramp to or from zero when playback is
4467-// stopped or started.
4468-const int kiRampLength = 50;
4469-
4470+//vinyl status constants
4471+//XXX: move this to vinylcontrol.h once thread startup is moved
4472+const int VINYL_STATUS_DISABLED = 0;
4473+const int VINYL_STATUS_OK = 1;
4474+const int VINYL_STATUS_WARNING = 2;
4475+const int VINYL_STATUS_ERROR = 3;
4476+
4477+const int ENGINE_RAMP_DOWN = -1;
4478+const int ENGINE_RAMP_NONE = 0;
4479+const int ENGINE_RAMP_UP = 1;
4480+
4481+//const int kiRampLength = 3;
4482
4483 class EngineBuffer : public EngineObject
4484 {
4485@@ -105,6 +115,7 @@
4486 void slotControlStart(double);
4487 void slotControlEnd(double);
4488 void slotControlSeek(double);
4489+ void slotControlVinylSeek(double);
4490 void slotControlSeekAbs(double);
4491
4492 // Request that the EngineBuffer load a track. Since the process is
4493@@ -205,6 +216,9 @@
4494 // Whether or not to repeat the track when at the end
4495 ControlPushButton* m_pRepeat;
4496
4497+ ControlObject *m_pVinylStatus; //Status of vinyl control
4498+ ControlObject *m_pVinylSeek;
4499+
4500 /** Fwd and back controls, start and end of track control */
4501 ControlPushButton *startButton, *endButton;
4502
4503@@ -219,11 +233,16 @@
4504
4505 /** Holds the last sample value of the previous buffer. This is used when ramping to
4506 * zero in case of an immediate stop of the playback */
4507- float m_fLastSampleValue;
4508+ float m_fLastSampleValue[2];
4509 /** Is true if the previous buffer was silent due to pausing */
4510 bool m_bLastBufferPaused;
4511+ float m_fRampValue;
4512+ int m_iRampState;
4513+ //int m_iRampIter;
4514
4515 TrackPointer m_pCurrentTrack;
4516+ /*QFile df;
4517+ QTextStream writer;*/
4518 };
4519
4520 #endif
4521
4522=== modified file 'mixxx/src/engine/enginebufferscalelinear.cpp'
4523--- mixxx/src/engine/enginebufferscalelinear.cpp 2011-04-03 20:40:17 +0000
4524+++ mixxx/src/engine/enginebufferscalelinear.cpp 2011-04-16 23:24:33 +0000
4525@@ -20,8 +20,6 @@
4526 #include "mathstuff.h"
4527 #include "sampleutil.h"
4528
4529-#define RATE_LERP_LENGTH 200
4530-
4531 EngineBufferScaleLinear::EngineBufferScaleLinear(ReadAheadManager *pReadAheadManager) :
4532 EngineBufferScale(),
4533 m_pReadAheadManager(pReadAheadManager)
4534@@ -30,16 +28,25 @@
4535 m_dTempo = 0.0f;
4536 m_fOldTempo = 1.0f;
4537 m_fOldBaseRate = 1.0f;
4538- m_fPreviousL = 0.0f;
4539- m_fPreviousR = 0.0f;
4540- m_scaleRemainder = 0.0f;
4541+ m_dCurSampleIndex = 0.0f;
4542+ m_dNextSampleIndex = 0.0f;
4543+
4544+ for (int i=0; i<2; i++)
4545+ m_fPrevSample[i] = 0.0f;
4546
4547 buffer_int = new CSAMPLE[kiLinearScaleReadAheadLength];
4548+ buffer_int_size = 0;
4549+
4550+ /*df.setFileName("mixxx-debug-scaler.csv");
4551+ df.open(QIODevice::WriteOnly | QIODevice::Text);
4552+ writer.setDevice(&df);
4553+ buffer_count=0;*/
4554 SampleUtil::applyGain(buffer_int, 0.0, kiLinearScaleReadAheadLength);
4555 }
4556
4557 EngineBufferScaleLinear::~EngineBufferScaleLinear()
4558 {
4559+ //df.close();
4560 delete [] buffer_int;
4561 }
4562
4563@@ -50,16 +57,18 @@
4564
4565 m_dTempo = _tempo;
4566
4567- if (m_dTempo>MAX_SEEK_SPEED)
4568+ if (m_dTempo>MAX_SEEK_SPEED) {
4569 m_dTempo = MAX_SEEK_SPEED;
4570- else if (m_dTempo < -MAX_SEEK_SPEED)
4571+ } else if (m_dTempo < -MAX_SEEK_SPEED) {
4572 m_dTempo = -MAX_SEEK_SPEED;
4573+ }
4574
4575 // Determine playback direction
4576- if (m_dTempo<0.)
4577+ if (m_dTempo<0.) {
4578 m_bBackwards = true;
4579- else
4580+ } else {
4581 m_bBackwards = false;
4582+ }
4583
4584 return m_dTempo;
4585 }
4586@@ -75,7 +84,6 @@
4587 void EngineBufferScaleLinear::clear()
4588 {
4589 m_bClear = true;
4590- m_scaleRemainder = 0.0f;
4591 }
4592
4593
4594@@ -90,9 +98,67 @@
4595 return ((((a * frac_pos) - b_neg) * frac_pos + c) * frac_pos + x0);
4596 }
4597
4598-/** Stretch a buffer worth of audio using linear interpolation */
4599-CSAMPLE * EngineBufferScaleLinear::scale(double, unsigned long buf_size,
4600- CSAMPLE*, unsigned long)
4601+/** Determine if we're changing directions (scratching) and then perform
4602+ a stretch */
4603+CSAMPLE * EngineBufferScaleLinear::scale(double playpos, unsigned long buf_size,
4604+ CSAMPLE* pBase, unsigned long iBaseLength)
4605+{
4606+ float rate_add_new = m_dBaseRate;
4607+ float rate_add_old = m_fOldBaseRate; //Smoothly interpolate to new playback rate
4608+
4609+ // Guard against buf_size == 0
4610+ if ((int)buf_size == 0)
4611+ return buffer;
4612+
4613+ if (rate_add_new * rate_add_old < 0) {
4614+ //calculate half buffer going one way, and half buffer going
4615+ //the other way.
4616+
4617+ //first half: rate goes from old rate to zero
4618+ m_fOldBaseRate = rate_add_old;
4619+ m_dBaseRate = 0.0;
4620+ buffer = do_scale(buffer, buf_size/2, pBase, iBaseLength);
4621+
4622+ //reset prev sample so we can now read in the other direction
4623+ //(may not be necessary?)
4624+ if ((int)ceil(m_dCurSampleIndex)*2+1 < buffer_int_size) {
4625+ m_fPrevSample[0] = buffer_int[(int)ceil(m_dNextSampleIndex)*2];
4626+ m_fPrevSample[1] = buffer_int[(int)ceil(m_dNextSampleIndex)*2+1];
4627+ }
4628+
4629+ //if the buffer has extra samples, do a read so RAMAN ends up back where
4630+ //it should be
4631+ int extra_samples = buffer_int_size - (int)ceil(m_dCurSampleIndex)*2 - 2;
4632+ if (extra_samples > 0) {
4633+ if (extra_samples % 2 != 0)
4634+ extra_samples++;
4635+ qDebug() << "extra samples" << extra_samples;
4636+
4637+ m_pReadAheadManager->getNextSamples(rate_add_new,buffer_int,
4638+ extra_samples);
4639+
4640+ }
4641+ //force a buffer read:
4642+ buffer_int_size=0;
4643+ //make sure the indexes stay correct for interpolation
4644+ m_dCurSampleIndex = 0 - m_dCurSampleIndex + floor(m_dCurSampleIndex);
4645+ m_dNextSampleIndex = 1.0 - (m_dNextSampleIndex - floor(m_dNextSampleIndex));
4646+
4647+ //second half: rate goes from zero to new rate
4648+ m_fOldBaseRate = 0.0;
4649+ m_dBaseRate = rate_add_new;
4650+ //pass the address of the sample at the halfway point
4651+ do_scale(&buffer[buf_size/2], buf_size/2, pBase, iBaseLength);
4652+
4653+ return buffer;
4654+ }
4655+
4656+ return do_scale(buffer, buf_size, pBase, iBaseLength);
4657+}
4658+
4659+/** Stretch a specified buffer worth of audio using linear interpolation */
4660+CSAMPLE * EngineBufferScaleLinear::do_scale(CSAMPLE* buf, unsigned long buf_size,
4661+ CSAMPLE* pBase, unsigned long iBaseLength)
4662 {
4663
4664 long unscaled_samples_needed;
4665@@ -102,9 +168,6 @@
4666 float rate_add_diff = rate_add_new - rate_add_old;
4667 double rate_add_abs;
4668
4669- if ( rate_add_diff )
4670- m_scaleRemainder = 0.0f;
4671-
4672 //Update the old base rate because we only need to
4673 //interpolate/ramp up the pitch changes once.
4674 m_fOldBaseRate = m_dBaseRate;
4675@@ -113,158 +176,194 @@
4676 // the new EngineBuffer implementation)
4677 new_playpos = 0.0;
4678
4679- const int iRateLerpLength = math_min(RATE_LERP_LENGTH, buf_size);
4680+ int iRateLerpLength = (int)buf_size;
4681
4682 // Guard against buf_size == 0
4683 if (iRateLerpLength == 0)
4684- return buffer;
4685+ return buf;
4686+
4687+
4688+ //We check for scratch condition in the public function, so this
4689+ //shouldn't happen
4690+ Q_ASSERT(rate_add_new * rate_add_old >= 0);
4691
4692 // Simulate the loop to estimate how many samples we need
4693 double samples = 0;
4694
4695- for (int j = 0; j < iRateLerpLength; j+=2)
4696- {
4697- rate_add = (rate_add_diff) / iRateLerpLength * j + rate_add_old;
4698+ for (int j = 0; j < iRateLerpLength; j += 2) {
4699+ rate_add = (rate_add_diff) / (float)iRateLerpLength * (float)j + rate_add_old;
4700 samples += fabs(rate_add);
4701 }
4702
4703 rate_add = rate_add_new;
4704 rate_add_abs = fabs(rate_add);
4705
4706- samples += (rate_add_abs * ((buf_size - iRateLerpLength)/2));
4707- unscaled_samples_needed = ceil(samples);
4708-
4709- if ( samples != unscaled_samples_needed)
4710- m_scaleRemainder += (double)unscaled_samples_needed - samples;
4711-
4712- bool carry_remainder = false;
4713- if ((m_scaleRemainder > 1) || (m_scaleRemainder < 1))
4714- {
4715- long rem = (long)floor(m_scaleRemainder);
4716-
4717-
4718- // Be very defensive about equating the remainder
4719- // back into unscaled_samples_needed
4720- if ((unscaled_samples_needed - rem) >= 1)
4721- {
4722- carry_remainder = true;
4723- m_scaleRemainder -= rem;
4724- }
4725+ //we're calculating mono samples, so divide remaining buffer by 2;
4726+ samples += (rate_add_abs * ((float)(buf_size - iRateLerpLength)/2));
4727+ unscaled_samples_needed = floor(samples);
4728+
4729+ //if the current position fraction plus the future position fraction
4730+ //loops over 1.0, we need to round up
4731+ if (m_dNextSampleIndex - floor(m_dNextSampleIndex) + samples - floor(samples) > 1.0) {
4732+ unscaled_samples_needed++;
4733 }
4734
4735 // Multiply by 2 because it is predicting mono rates, while we want a stereo
4736 // number of samples.
4737 unscaled_samples_needed *= 2;
4738
4739- Q_ASSERT(unscaled_samples_needed >= 0);
4740- Q_ASSERT(unscaled_samples_needed != 0);
4741-
4742- int buffer_size = 0;
4743- double buffer_index = 0;
4744-
4745- long current_sample = 0;
4746- long prev_sample = 0;
4747+ //0 is never the right answer
4748+ unscaled_samples_needed = math_max(2,unscaled_samples_needed);
4749+
4750 bool last_read_failed = false;
4751+ CSAMPLE prev_sample[2];
4752+ CSAMPLE cur_sample[2];
4753+ double prevIndex=0;
4754
4755- // Use new_playpos to count the new samples we touch.
4756- new_playpos = 0;
4757+ prev_sample[0]=0;
4758+ prev_sample[1]=0;
4759+ cur_sample[0]=0;
4760+ cur_sample[1]=0;
4761
4762 int i = 0;
4763 int screwups = 0;
4764- while(i < buf_size)
4765- {
4766- prev_sample = current_sample;
4767-
4768- current_sample = floor(buffer_index) * 2;
4769- if (!even(current_sample))
4770- current_sample++;
4771-
4772- Q_ASSERT(current_sample % 2 == 0);
4773- Q_ASSERT(current_sample >= 0);
4774-
4775- //This code is so messed up. These ASSERTs should be enabled, but they actually
4776- //fire because of bug(s).
4777- //Q_ASSERT(prev_sample >= 0);
4778- //Q_ASSERT(prev_sample-1 < kiLinearScaleReadAheadLength);
4779- //the prev_sample-1 leaves room for the other sample in the stereo frame
4780- //Instead, we're going to workaround the bug by just clamping prev_sample
4781- //to make sure it stays in bounds:
4782- prev_sample = math_min(kiLinearScaleReadAheadLength, prev_sample);
4783- prev_sample = math_max(0, prev_sample);
4784-
4785-
4786- if (prev_sample != current_sample) {
4787- m_fPreviousL = buffer_int[prev_sample];
4788- m_fPreviousR = buffer_int[prev_sample+1];
4789- }
4790-
4791- if (current_sample+1 >= buffer_size) {
4792+ while(i < buf_size) {
4793+ //shift indicies
4794+ prevIndex = m_dCurSampleIndex;
4795+ m_dCurSampleIndex = m_dNextSampleIndex;
4796+
4797+ //we're going to be interpolating between two samples, a lower (prev)
4798+ //and upper (cur) sample. If the lower sample is off the end of the buffer,
4799+ //load it from the saved globals
4800+
4801+ if ((int)floor(m_dCurSampleIndex)*2+1 < buffer_int_size && m_dCurSampleIndex >= 0.0) {
4802+ m_fPrevSample[0] = prev_sample[0] = buffer_int[(int)floor(m_dCurSampleIndex)*2];
4803+ m_fPrevSample[1] = prev_sample[1] = buffer_int[(int)floor(m_dCurSampleIndex)*2+1];
4804+ } else {
4805+ prev_sample[0] = m_fPrevSample[0];
4806+ prev_sample[1] = m_fPrevSample[1];
4807+ }
4808+
4809+ //Smooth any changes in the playback rate over iRateLerpLength
4810+ //samples. This prevents the change from being discontinuous and helps
4811+ //improve sound quality.
4812+ if (i < iRateLerpLength) {
4813+ rate_add = (float)i * (rate_add_diff) / (float)iRateLerpLength + rate_add_old;
4814+ //rate_add = sigmoid_zero((float)i,(float)iRateLerpLength) * rate_add_diff + rate_add_old;
4815+ } else {
4816+ rate_add = rate_add_new;
4817+ }
4818+
4819+ // if we don't have enough samples, load some more
4820+ while ((int)ceil(m_dCurSampleIndex)*2+1 >= buffer_int_size) {
4821+ int old_bufsize = buffer_int_size;
4822+ //qDebug() << "buffer" << buffer_count << rate_add_old << rate_add_new << rate_add << i << m_dCurSampleIndex << buffer_int_size << unscaled_samples_needed;
4823 //Q_ASSERT(unscaled_samples_needed > 0);
4824 if (unscaled_samples_needed == 0) {
4825- unscaled_samples_needed = 2;
4826- screwups++;
4827+ //qDebug() << "screwup" << m_dCurSampleIndex << (int)ceil(m_dCurSampleIndex)*2+1 << buffer_int_size;
4828+ unscaled_samples_needed = 2;
4829+ screwups++;
4830 }
4831
4832 int samples_to_read = math_min(kiLinearScaleReadAheadLength,
4833 unscaled_samples_needed);
4834
4835- buffer_size = m_pReadAheadManager
4836- ->getNextSamples(m_dBaseRate,buffer_int,
4837- samples_to_read);
4838-
4839- if (m_dBaseRate > 0)
4840- new_playpos += buffer_size;
4841- else if (m_dBaseRate < 0)
4842- new_playpos -= buffer_size;
4843-
4844-
4845- if (buffer_size == 0 && last_read_failed) {
4846+ if(rate_add_new == 0) {
4847+ //qDebug() << "new rate was zero";
4848+ buffer_int_size = m_pReadAheadManager
4849+ ->getNextSamples(rate_add_old,buffer_int,
4850+ samples_to_read);
4851+ if (rate_add_old > 0) {
4852+ new_playpos += buffer_int_size;
4853+ } else if (rate_add_old < 0) {
4854+ new_playpos -= buffer_int_size;
4855+ }
4856+ } else {
4857+ buffer_int_size = m_pReadAheadManager
4858+ ->getNextSamples(rate_add_new,buffer_int,
4859+ samples_to_read);
4860+ if (rate_add_new > 0)
4861+ new_playpos += buffer_int_size;
4862+ else if (rate_add_new < 0)
4863+ new_playpos -= buffer_int_size;
4864+ }
4865+
4866+ if (buffer_int_size == 0 && last_read_failed) {
4867 break;
4868 }
4869- last_read_failed = buffer_size == 0;
4870-
4871- unscaled_samples_needed -= buffer_size;
4872- buffer_index = buffer_index - floor(buffer_index);
4873-
4874- continue;
4875- }
4876-
4877- //Smooth any changes in the playback rate over iRateLerpLength
4878- //samples. This prevents the change from being discontinuous and helps
4879- //improve sound quality.
4880- if (i < iRateLerpLength) {
4881- rate_add = (rate_add_diff) / iRateLerpLength * i + rate_add_old;
4882- }
4883- else {
4884- rate_add = rate_add_new;
4885- }
4886-
4887- CSAMPLE frac = buffer_index - floor(buffer_index);
4888+ last_read_failed = buffer_int_size == 0;
4889+
4890+ unscaled_samples_needed -= buffer_int_size;
4891+ //shift the index by the size of the old buffer
4892+ m_dCurSampleIndex -= old_bufsize / 2;
4893+ prevIndex -= old_bufsize / 2;
4894+ //fractions below 0 is ok, the ceil will bring it up to 0
4895+ //this happens sometimes, somehow?
4896+ //Q_ASSERT(m_dCurSampleIndex > -1.0);
4897+
4898+ //not sure this actually does anything, but it seems to help
4899+ if ((int)floor(m_dCurSampleIndex)*2 >= 0.0) {
4900+ m_fPrevSample[0] = prev_sample[0] = buffer_int[(int)floor(m_dCurSampleIndex)*2];
4901+ m_fPrevSample[1] = prev_sample[1] = buffer_int[(int)floor(m_dCurSampleIndex)*2+1];
4902+ }
4903+ }
4904+ //I guess?
4905+ if (last_read_failed)
4906+ break;
4907+
4908+ cur_sample[0] = buffer_int[(int)ceil(m_dCurSampleIndex)*2];
4909+ cur_sample[1] = buffer_int[(int)ceil(m_dCurSampleIndex)*2+1];
4910+
4911+ //rate_add was here
4912+
4913+ //for the current index, what percentage is it between the previous and the next?
4914+ CSAMPLE frac = m_dCurSampleIndex - floor(m_dCurSampleIndex);
4915
4916 //Perform linear interpolation
4917- buffer[i] = m_fPreviousL + frac * (buffer_int[current_sample] - m_fPreviousL);
4918- buffer[i+1] = m_fPreviousR + frac * (buffer_int[current_sample+1] - m_fPreviousR);
4919-
4920+ buf[i] = (float)prev_sample[0] + frac * ((float)cur_sample[0] - (float)prev_sample[0]);
4921+ buf[i+1] = (float)prev_sample[1] + frac * ((float)cur_sample[1] - (float)prev_sample[1]);
4922+
4923+ //at extremely low speeds, dampen the gain to hide pops and clicks
4924+ //this does cause odd-looking linear waveforms that go to zero and back
4925+
4926+ //although enginevinylsoundemu does this, it works much better here
4927+ //because the gain ramps as the rate does
4928+ if (fabs(rate_add) < 0.5) {
4929+ float dither = (float)(rand() % 32768) / 32768 - 0.5; // dither
4930+ //float dither = 0;
4931+ float gainfrac = fabs(rate_add) / 0.5;
4932+ buf[i] = gainfrac * (float)buf[i] + dither;
4933+ buf[i+1] = gainfrac * (float)buf[i+1] + dither;
4934+ }
4935+
4936+ /*writer << QString("%1,%2,%3,%4\n").arg(buffer_count)
4937+ .arg(buffer[i])
4938+ .arg(prev_sample[0])
4939+ .arg(cur_sample[0]);
4940+ buffer_count++;*/
4941+
4942+ //increment the index for the next loop
4943 if (i < iRateLerpLength)
4944- buffer_index += fabs(rate_add);
4945+ m_dNextSampleIndex = m_dCurSampleIndex + fabs(rate_add);
4946 else
4947- buffer_index += rate_add_abs;
4948+ m_dNextSampleIndex = m_dCurSampleIndex + rate_add_abs;
4949
4950 i+=2;
4951- }
4952-
4953- if ( carry_remainder )
4954- {
4955- m_fPreviousL = buffer_int[buffer_size-2];
4956- m_fPreviousR = buffer_int[buffer_size-1];
4957- }
4958-
4959+
4960+ }
4961 // If we broke out of the loop, zero the remaining samples
4962- SampleUtil::applyGain(&buffer[i], 0.0f, buf_size-i);
4963+ // TODO(XXX) memset
4964+ //for (; i < buf_size; i += 2) {
4965+ // buf[i] = 0.0f;
4966+ // buf[i+1] = 0.0f;
4967+ //}
4968+
4969+ //Q_ASSERT(i>=buf_size);
4970+ SampleUtil::applyGain(&buf[i], 0.0f, buf_size-i);
4971
4972 // It's possible that we will exit this function without having satisfied
4973 // this requirement. We may be trying to read past the end of the file.
4974 //Q_ASSERT(unscaled_samples_needed == 0);
4975
4976- return buffer;
4977+ return buf;
4978 }
4979
4980=== modified file 'mixxx/src/engine/enginebufferscalelinear.h'
4981--- mixxx/src/engine/enginebufferscalelinear.h 2010-05-23 06:36:06 +0000
4982+++ mixxx/src/engine/enginebufferscalelinear.h 2011-04-16 23:24:33 +0000
4983@@ -34,13 +34,17 @@
4984 public:
4985 EngineBufferScaleLinear(ReadAheadManager *pReadAheadManager);
4986 ~EngineBufferScaleLinear();
4987- CSAMPLE *scale(double playpos, unsigned long buf_size,
4988+ CSAMPLE *scale(double playpos, unsigned long buf_size,
4989 CSAMPLE* pBase, unsigned long iBaseLength);
4990+
4991 void setBaseRate(double dBaseRate);
4992 double setTempo(double dTempo);
4993 void clear();
4994
4995 private:
4996+ CSAMPLE *do_scale(CSAMPLE* buf, unsigned long buf_size,
4997+ CSAMPLE* pBase, unsigned long iBaseLength);
4998+
4999 /** Holds playback direction */
5000 bool m_bBackwards;
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches