//override bool renderFeaturesForStyle( const Style& style, const FeatureList& inFeatures, osg::Referenced* buildData, const GeoExtent& imageExtent, osg::Image* image ) { // local copy of the features that we can process FeatureList features = inFeatures; BuildData* bd = static_cast<BuildData*>( buildData ); // A processing context to use with the filters: FilterContext context; context.profile() = getFeatureSource()->getFeatureProfile(); const LineSymbol* masterLine = style.getSymbol<LineSymbol>(); const PolygonSymbol* masterPoly = style.getSymbol<PolygonSymbol>(); //bool embeddedStyles = getFeatureSource()->hasEmbeddedStyles(); // if only a line symbol exists, and there are polygons in the mix, draw them // as outlines (line rings). //OE_INFO << LC << "Line Symbol = " << (masterLine == 0L ? "null" : masterLine->getConfig().toString()) << std::endl; //OE_INFO << LC << "Poly SYmbol = " << (masterPoly == 0L ? "null" : masterPoly->getConfig().toString()) << std::endl; //bool convertPolysToRings = poly == 0L && line != 0L; //if ( convertPolysToRings ) // OE_INFO << LC << "No PolygonSymbol; will draw polygons to rings" << std::endl; // initialize: double xmin = imageExtent.xMin(); double ymin = imageExtent.yMin(); //double s = (double)image->s(); //double t = (double)image->t(); double xf = (double)image->s() / imageExtent.width(); double yf = (double)image->t() / imageExtent.height(); // strictly speaking we should iterate over the features and buffer each one that's a line, // rather then checking for the existence of a LineSymbol. FeatureList linesToBuffer; for(FeatureList::iterator i = features.begin(); i != features.end(); i++) { Feature* feature = i->get(); Geometry* geom = feature->getGeometry(); if ( geom ) { // check for an embedded style: const LineSymbol* line = feature->style().isSet() ? feature->style()->getSymbol<LineSymbol>() : masterLine; const PolygonSymbol* poly = feature->style().isSet() ? feature->style()->getSymbol<PolygonSymbol>() : masterPoly; // if we have polygons but only a LineSymbol, draw the poly as a line. if ( geom->getComponentType() == Geometry::TYPE_POLYGON ) { if ( !poly && line ) { Feature* outline = new Feature( *feature ); geom = geom->cloneAs( Geometry::TYPE_RING ); outline->setGeometry( geom ); *i = outline; feature = outline; } //TODO: fix to enable outlined polys. doesn't work, not sure why -gw //else if ( poly && line ) //{ // Feature* outline = new Feature(); // geom = geom->cloneAs( Geometry::TYPE_LINESTRING ); // outline->setGeometry( geom ); // features.push_back( outline ); //} } bool needsBuffering = geom->getComponentType() == Geometry::TYPE_LINESTRING || geom->getComponentType() == Geometry::TYPE_RING; if ( needsBuffering ) { linesToBuffer.push_back( feature ); } } } if ( linesToBuffer.size() > 0 ) { //We are buffering in the features native extent, so we need to use the transform extent to get the proper "resolution" for the image GeoExtent transformedExtent = imageExtent.transform(context.profile()->getSRS()); double trans_xf = (double)image->s() / transformedExtent.width(); double trans_yf = (double)image->t() / transformedExtent.height(); // resolution of the image (pixel extents): double xres = 1.0/trans_xf; double yres = 1.0/trans_yf; // downsample the line data so that it is no higher resolution than to image to which // we intend to rasterize it. If you don't do this, you run the risk of the buffer // operation taking forever on very high-res input data. if ( _options.optimizeLineSampling() == true ) { ResampleFilter resample; resample.minLength() = osg::minimum( xres, yres ); context = resample.push( linesToBuffer, context ); } // now run the buffer operation on all lines: BufferFilter buffer; float lineWidth = 0.5; if ( masterLine ) { buffer.capStyle() = masterLine->stroke()->lineCap().value(); if ( masterLine->stroke()->width().isSet() ) lineWidth = masterLine->stroke()->width().value(); } // "relative line size" means that the line width is expressed in (approx) pixels // rather than in map units if ( _options.relativeLineSize() == true ) buffer.distance() = xres * lineWidth; else buffer.distance() = lineWidth; buffer.push( linesToBuffer, context ); } // First, transform the features into the map's SRS: TransformFilter xform( imageExtent.getSRS() ); xform.setLocalizeCoordinates( false ); context = xform.push( features, context ); // set up the AGG renderer: agg::rendering_buffer rbuf( image->data(), image->s(), image->t(), image->s()*4 ); // Create the renderer and the rasterizer agg::renderer<agg::span_abgr32> ren(rbuf); agg::rasterizer ras; // Setup the rasterizer ras.gamma(1.3); ras.filling_rule(agg::fill_even_odd); GeoExtent cropExtent = GeoExtent(imageExtent); cropExtent.scale(1.1, 1.1); osg::ref_ptr<Symbology::Polygon> cropPoly = new Symbology::Polygon( 4 ); cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMin(), 0 )); cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMin(), 0 )); cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMax(), 0 )); cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMax(), 0 )); double lineWidth = 1.0; if ( masterLine ) lineWidth = (double)masterLine->stroke()->width().value(); osg::Vec4 color = osg::Vec4(1, 1, 1, 1); if ( masterLine ) color = masterLine->stroke()->color(); // render the features for(FeatureList::iterator i = features.begin(); i != features.end(); i++) { Feature* feature = i->get(); //bool first = bd->_pass == 0 && i == features.begin(); Geometry* geometry = feature->getGeometry(); osg::ref_ptr< Geometry > croppedGeometry; if ( ! geometry->crop( cropPoly.get(), croppedGeometry ) ) continue; // set up a default color: osg::Vec4 c = color; unsigned int a = (unsigned int)(127+(c.a()*255)/2); // scale alpha up agg::rgba8 fgColor( (unsigned int)(c.r()*255), (unsigned int)(c.g()*255), (unsigned int)(c.b()*255), a ); GeometryIterator gi( croppedGeometry.get() ); while( gi.hasMore() ) { c = color; Geometry* g = gi.next(); const LineSymbol* line = feature->style().isSet() ? feature->style()->getSymbol<LineSymbol>() : masterLine; const PolygonSymbol* poly = feature->style().isSet() ? feature->style()->getSymbol<PolygonSymbol>() : masterPoly; if (g->getType() == Geometry::TYPE_RING || g->getType() == Geometry::TYPE_LINESTRING) { if ( line ) c = line->stroke()->color(); else if ( poly ) c = poly->fill()->color(); } else if ( g->getType() == Geometry::TYPE_POLYGON ) { if ( poly ) c = poly->fill()->color(); else if ( line ) c = line->stroke()->color(); } a = (unsigned int)(127+(c.a()*255)/2); // scale alpha up fgColor = agg::rgba8( (unsigned int)(c.r()*255), (unsigned int)(c.g()*255), (unsigned int)(c.b()*255), a ); ras.filling_rule( agg::fill_even_odd ); for( Geometry::iterator p = g->begin(); p != g->end(); p++ ) { const osg::Vec3d& p0 = *p; double x0 = xf*(p0.x()-xmin); double y0 = yf*(p0.y()-ymin); //const osg::Vec3d& p1 = p+1 != g->end()? *(p+1) : g->front(); //double x1 = xf*(p1.x()-xmin); //double y1 = yf*(p1.y()-ymin); if ( p == g->begin() ) ras.move_to_d( x0, y0 ); else ras.line_to_d( x0, y0 ); } } ras.render(ren, fgColor); ras.reset(); } bd->_pass++; return true; }
FilterContext push(FeatureList& input, FilterContext& context) { if (_featureSource.valid()) { // Get any features that intersect this query. FeatureList boundaries; getFeatures(context.extent().get(), boundaries ); // The list of output features FeatureList output; if (boundaries.empty()) { // No intersecting features. If contains is false, then just the output to the input. if (contains() == false) { output = input; } } else { // Transform the boundaries into the coordinate system of the features for (FeatureList::iterator itr = boundaries.begin(); itr != boundaries.end(); ++itr) { itr->get()->transform( context.profile()->getSRS() ); } for(FeatureList::const_iterator f = input.begin(); f != input.end(); ++f) { Feature* feature = f->get(); if ( feature && feature->getGeometry() ) { osg::Vec2d c = feature->getGeometry()->getBounds().center2d(); if ( contains() == true ) { // coarsest: if (_featureSource->getFeatureProfile()->getExtent().contains(GeoPoint(feature->getSRS(), c.x(), c.y()))) { for (FeatureList::iterator itr = boundaries.begin(); itr != boundaries.end(); ++itr) { Ring* ring = dynamic_cast< Ring*>(itr->get()->getGeometry()); if (ring && ring->contains2D(c.x(), c.y())) { output.push_back( feature ); } } } } else { bool contained = false; // coarsest: if (_featureSource->getFeatureProfile()->getExtent().contains(GeoPoint(feature->getSRS(), c.x(), c.y()))) { for (FeatureList::iterator itr = boundaries.begin(); itr != boundaries.end(); ++itr) { Ring* ring = dynamic_cast< Ring*>(itr->get()->getGeometry()); if (ring && ring->contains2D(c.x(), c.y())) { contained = true; break; } } } if ( !contained ) { output.push_back( feature ); } } } } } OE_INFO << LC << "Allowed " << output.size() << " out of " << input.size() << " features\n"; input = output; } return context; }
FeatureCursor* createFeatureCursor(const Symbology::Query& query) { FeatureCursor* result = 0L; std::string url = createURL( query ); if (url.empty()) return 0; // check the blacklist: if ( Registry::instance()->isBlacklisted(url) ) return 0L; OE_DEBUG << LC << url << std::endl; URI uri(url); // read the data: ReadResult r = uri.readString( _readOptions.get() ); const std::string& buffer = r.getString(); const Config& meta = r.metadata(); bool dataOK = false; FeatureList features; if ( !buffer.empty() ) { // Get the mime-type from the metadata record if possible std::string mimeType = r.metadata().value( IOMetadata::CONTENT_TYPE ); //If the mimetype is empty then try to set it from the format specification if (mimeType.empty()) { if (_options.format().value() == "json") mimeType = "json"; else if (_options.format().value().compare("gml") == 0) mimeType = "text/xml"; else if (_options.format().value().compare("pbf") == 0) mimeType = "application/x-protobuf"; } dataOK = getFeatures( buffer, *query.tileKey(), mimeType, features ); } if ( dataOK ) { OE_DEBUG << LC << "Read " << features.size() << " features" << std::endl; } //If we have any filters, process them here before the cursor is created if (!getFilters().empty()) { // preprocess the features using the filter list: if ( features.size() > 0 ) { FilterContext cx; cx.setProfile( getFeatureProfile() ); cx.extent() = query.tileKey()->getExtent(); for( FeatureFilterList::const_iterator i = getFilters().begin(); i != getFilters().end(); ++i ) { FeatureFilter* filter = i->get(); cx = filter->push( features, cx ); } } } // If we have any features and we have an fid attribute, override the fid of the features if (_options.fidAttribute().isSet()) { for (FeatureList::iterator itr = features.begin(); itr != features.end(); ++itr) { std::string attr = itr->get()->getString(_options.fidAttribute().get()); FeatureID fid = as<long>(attr, 0); itr->get()->setFID( fid ); } } //result = new FeatureListCursor(features); result = dataOK ? new FeatureListCursor( features ) : 0L; if ( !result ) Registry::instance()->blacklist( url ); return result; }
// reads a chunk of features into a memory cache; do this for performance // and to avoid needing the OGR Mutex every time void FeatureCursorOGR::readChunk() { if ( !_resultSetHandle ) return; FeatureList preProcessList; OGR_SCOPED_LOCK; if ( _nextHandleToQueue ) { osg::ref_ptr<Feature> f = OgrUtils::createFeature( _nextHandleToQueue, _profile->getSRS() ); if ( f.valid() && !_source->isBlacklisted(f->getFID()) ) { if ( isGeometryValid( f->getGeometry() ) ) { _queue.push( f ); if ( _filters.size() > 0 ) preProcessList.push_back( f.release() ); } else { OE_INFO << LC << "Skipping feature with invalid geometry: " << f->getGeoJSON() << std::endl; } } OGR_F_Destroy( _nextHandleToQueue ); _nextHandleToQueue = 0L; } unsigned handlesToQueue = _chunkSize - _queue.size(); bool resultSetEndReached = false; for( unsigned i=0; i<handlesToQueue; i++ ) { OGRFeatureH handle = OGR_L_GetNextFeature( _resultSetHandle ); if ( handle ) { osg::ref_ptr<Feature> f = OgrUtils::createFeature( handle, _profile->getSRS() ); if ( f.valid() && !_source->isBlacklisted(f->getFID()) ) { if (isGeometryValid( f->getGeometry() ) ) { _queue.push( f ); if ( _filters.size() > 0 ) preProcessList.push_back( f.release() ); } else { OE_INFO << LC << "Skipping feature with invalid geometry: " << f->getGeoJSON() << std::endl; } } OGR_F_Destroy( handle ); } else { resultSetEndReached = true; break; } } // preprocess the features using the filter list: if ( preProcessList.size() > 0 ) { FilterContext cx; cx.setProfile( _profile.get() ); for( FeatureFilterList::const_iterator i = _filters.begin(); i != _filters.end(); ++i ) { FeatureFilter* filter = i->get(); cx = filter->push( preProcessList, cx ); } } // read one more for "more" detection: if (!resultSetEndReached) _nextHandleToQueue = OGR_L_GetNextFeature( _resultSetHandle ); else _nextHandleToQueue = 0L; //OE_NOTICE << "read " << _queue.size() << " features ... " << std::endl; }
//override bool renderFeaturesForStyle( Session* session, const Style& style, const FeatureList& features, osg::Referenced* buildData, const GeoExtent& imageExtent, osg::Image* image ) { OE_DEBUG << LC << "Rendering " << features.size() << " features for " << imageExtent.toString() << "\n"; // A processing context to use with the filters: FilterContext context( session ); context.setProfile( getFeatureSource()->getFeatureProfile() ); const LineSymbol* masterLine = style.getSymbol<LineSymbol>(); const PolygonSymbol* masterPoly = style.getSymbol<PolygonSymbol>(); const CoverageSymbol* masterCov = style.getSymbol<CoverageSymbol>(); // sort into bins, making a copy for lines that require buffering. FeatureList polygons; FeatureList lines; for(FeatureList::const_iterator f = features.begin(); f != features.end(); ++f) { if ( f->get()->getGeometry() ) { bool hasPoly = false; bool hasLine = false; if ( masterPoly || f->get()->style()->has<PolygonSymbol>() ) { polygons.push_back( f->get() ); hasPoly = true; } if ( masterLine || f->get()->style()->has<LineSymbol>() ) { Feature* newFeature = new Feature( *f->get() ); if ( !newFeature->getGeometry()->isLinear() ) { newFeature->setGeometry( newFeature->getGeometry()->cloneAs(Geometry::TYPE_RING) ); } lines.push_back( newFeature ); hasLine = true; } // if there are no geometry symbols but there is a coverage symbol, default to polygons. if ( !hasLine && !hasPoly ) { if ( masterCov || f->get()->style()->has<CoverageSymbol>() ) { polygons.push_back( f->get() ); } } } } // initialize: RenderFrame frame; frame.xmin = imageExtent.xMin(); frame.ymin = imageExtent.yMin(); frame.xf = (double)image->s() / imageExtent.width(); frame.yf = (double)image->t() / imageExtent.height(); if ( lines.size() > 0 ) { // We are buffering in the features native extent, so we need to use the // transformed extent to get the proper "resolution" for the image const SpatialReference* featureSRS = context.profile()->getSRS(); GeoExtent transformedExtent = imageExtent.transform(featureSRS); double trans_xf = (double)image->s() / transformedExtent.width(); double trans_yf = (double)image->t() / transformedExtent.height(); // resolution of the image (pixel extents): double xres = 1.0/trans_xf; double yres = 1.0/trans_yf; // downsample the line data so that it is no higher resolution than to image to which // we intend to rasterize it. If you don't do this, you run the risk of the buffer // operation taking forever on very high-res input data. if ( _options.optimizeLineSampling() == true ) { ResampleFilter resample; resample.minLength() = osg::minimum( xres, yres ); context = resample.push( lines, context ); } // now run the buffer operation on all lines: BufferFilter buffer; double lineWidth = 1.0; if ( masterLine ) { buffer.capStyle() = masterLine->stroke()->lineCap().value(); if ( masterLine->stroke()->width().isSet() ) { lineWidth = masterLine->stroke()->width().value(); GeoExtent imageExtentInFeatureSRS = imageExtent.transform(featureSRS); double pixelWidth = imageExtentInFeatureSRS.width() / (double)image->s(); // if the width units are specified, process them: if (masterLine->stroke()->widthUnits().isSet() && masterLine->stroke()->widthUnits().get() != Units::PIXELS) { const Units& featureUnits = featureSRS->getUnits(); const Units& strokeUnits = masterLine->stroke()->widthUnits().value(); // if the units are different than those of the feature data, we need to // do a units conversion. if ( featureUnits != strokeUnits ) { if ( Units::canConvert(strokeUnits, featureUnits) ) { // linear to linear, no problem lineWidth = strokeUnits.convertTo( featureUnits, lineWidth ); } else if ( strokeUnits.isLinear() && featureUnits.isAngular() ) { // linear to angular? approximate degrees per meter at the // latitude of the tile's centroid. double lineWidthM = masterLine->stroke()->widthUnits()->convertTo(Units::METERS, lineWidth); double mPerDegAtEquatorInv = 360.0/(featureSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI); double lon, lat; imageExtent.getCentroid(lon, lat); lineWidth = lineWidthM * mPerDegAtEquatorInv * cos(osg::DegreesToRadians(lat)); } } // enfore a minimum width of one pixel. float minPixels = masterLine->stroke()->minPixels().getOrUse( 1.0f ); lineWidth = osg::clampAbove(lineWidth, pixelWidth*minPixels); } else // pixels { lineWidth *= pixelWidth; } } } buffer.distance() = lineWidth * 0.5; // since the distance is for one side buffer.push( lines, context ); } // Transform the features into the map's SRS: TransformFilter xform( imageExtent.getSRS() ); xform.setLocalizeCoordinates( false ); FilterContext polysContext = xform.push( polygons, context ); FilterContext linesContext = xform.push( lines, context ); // set up the AGG renderer: agg::rendering_buffer rbuf( image->data(), image->s(), image->t(), image->s()*4 ); // Create the renderer and the rasterizer agg::rasterizer ras; // Setup the rasterizer if ( _options.coverage() == true ) ras.gamma(1.0); else ras.gamma(_options.gamma().get()); ras.filling_rule(agg::fill_even_odd); // construct an extent for cropping the geometry to our tile. // extend just outside the actual extents so we don't get edge artifacts: GeoExtent cropExtent = GeoExtent(imageExtent); cropExtent.scale(1.1, 1.1); osg::ref_ptr<Symbology::Polygon> cropPoly = new Symbology::Polygon( 4 ); cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMin(), 0 )); cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMin(), 0 )); cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMax(), 0 )); cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMax(), 0 )); // If there's a coverage symbol, make a copy of the expressions so we can evaluate them optional<NumericExpression> covValue; const CoverageSymbol* covsym = style.get<CoverageSymbol>(); if (covsym && covsym->valueExpression().isSet()) covValue = covsym->valueExpression().get(); // render the polygons for(FeatureList::iterator i = polygons.begin(); i != polygons.end(); i++) { Feature* feature = i->get(); Geometry* geometry = feature->getGeometry(); osg::ref_ptr<Geometry> croppedGeometry; if ( geometry->crop( cropPoly.get(), croppedGeometry ) ) { const PolygonSymbol* poly = feature->style().isSet() && feature->style()->has<PolygonSymbol>() ? feature->style()->get<PolygonSymbol>() : masterPoly; if ( _options.coverage() == true && covValue.isSet() ) { float value = (float)feature->eval(covValue.mutable_value(), &context); rasterizeCoverage(croppedGeometry.get(), value, frame, ras, rbuf); } else { osg::Vec4f color = poly->fill()->color(); rasterize(croppedGeometry.get(), color, frame, ras, rbuf); } } } // render the lines for(FeatureList::iterator i = lines.begin(); i != lines.end(); i++) { Feature* feature = i->get(); Geometry* geometry = feature->getGeometry(); osg::ref_ptr<Geometry> croppedGeometry; if ( geometry->crop( cropPoly.get(), croppedGeometry ) ) { const LineSymbol* line = feature->style().isSet() && feature->style()->has<LineSymbol>() ? feature->style()->get<LineSymbol>() : masterLine; if ( _options.coverage() == true && covValue.isSet() ) { float value = (float)feature->eval(covValue.mutable_value(), &context); rasterizeCoverage(croppedGeometry.get(), value, frame, ras, rbuf); } else { osg::Vec4f color = line ? static_cast<osg::Vec4>(line->stroke()->color()) : osg::Vec4(1,1,1,1); rasterize(croppedGeometry.get(), color, frame, ras, rbuf); } } } return true; }
#include <osgEarth/catch.hpp> #include <osgEarthFeatures/Feature> #include <osgEarthFeatures/GeometryUtils> using namespace osgEarth; using namespace osgEarth::Symbology; using namespace osgEarth::Features; TEST_CASE("Feature::splitAcrossDateLine doesn't modify features that don't cross the dateline") { osg::ref_ptr< Feature > feature = new Feature(GeometryUtils::geometryFromWKT("POLYGON((-81 26, -40.5 45, -40.5 75.5, -81 60))"), osgEarth::SpatialReference::create("wgs84")); FeatureList features; feature->splitAcrossDateLine(features); // We only have one feature in the list. REQUIRE( features.size() == 1 ); // The feature is exactly the same feature that was passed in REQUIRE(features.front().get() == feature.get()); } TEST_CASE("Feature::splitAcrossDateLine works") { osg::ref_ptr< Feature > feature = new Feature(GeometryUtils::geometryFromWKT("POLYGON((170 26, 190 26, 190 56, 170 56))"), osgEarth::SpatialReference::create("wgs84")); FeatureList features; feature->splitAcrossDateLine(features); // We have two features in the list REQUIRE(features.size() == 2); // The features don't cross the anti-meridian for (FeatureList::iterator itr = features.begin(); itr != features.end(); ++itr) { REQUIRE_FALSE(itr->get()->getExtent().crossesAntimeridian()); }
Chordino::FeatureSet Chordino::getRemainingFeatures() { // cerr << hw[0] << hw[1] << endl; if (debug_on) cerr << "--> getRemainingFeatures" << endl; FeatureSet fsOut; if (m_logSpectrum.size() == 0) return fsOut; int nChord = m_chordnames.size(); // /** Calculate Tuning calculate tuning from (using the angle of the complex number defined by the cumulative mean real and imag values) **/ float meanTuningImag = 0; float meanTuningReal = 0; for (int iBPS = 0; iBPS < nBPS; ++iBPS) { meanTuningReal += m_meanTunings[iBPS] * cosvalues[iBPS]; meanTuningImag += m_meanTunings[iBPS] * sinvalues[iBPS]; } float cumulativetuning = 440 * pow(2,atan2(meanTuningImag, meanTuningReal)/(24*M_PI)); float normalisedtuning = atan2(meanTuningImag, meanTuningReal)/(2*M_PI); int intShift = floor(normalisedtuning * 3); float floatShift = normalisedtuning * 3 - intShift; // floatShift is a really bad name for this char buffer0 [50]; sprintf(buffer0, "estimated tuning: %0.1f Hz", cumulativetuning); /** Tune Log-Frequency Spectrogram calculate a tuned log-frequency spectrogram (currentTunedSpec): use the tuning estimated above (kinda f0) to perform linear interpolation on the existing log-frequency spectrogram (kinda currentLogSpectrum). **/ if (debug_on) cerr << endl << "[Chordino Plugin] Tuning Log-Frequency Spectrogram ... "; int count = 0; FeatureList tunedSpec; int nFrame = m_logSpectrum.size(); vector<Vamp::RealTime> timestamps; for (FeatureList::iterator i = m_logSpectrum.begin(); i != m_logSpectrum.end(); ++i) { Feature currentLogSpectrum = *i; Feature currentTunedSpec; // tuned log-frequency spectrum currentTunedSpec.hasTimestamp = true; currentTunedSpec.timestamp = currentLogSpectrum.timestamp; timestamps.push_back(currentLogSpectrum.timestamp); currentTunedSpec.values.push_back(0.0); currentTunedSpec.values.push_back(0.0); // set lower edge to zero if (m_tuneLocal) { intShift = floor(m_localTuning[count] * 3); floatShift = m_localTuning[count] * 3 - intShift; // floatShift is a really bad name for this } // cerr << intShift << " " << floatShift << endl; for (int k = 2; k < (int)currentLogSpectrum.values.size() - 3; ++k) { // interpolate all inner bins float tempValue = currentLogSpectrum.values[k + intShift] * (1-floatShift) + currentLogSpectrum.values[k+intShift+1] * floatShift; currentTunedSpec.values.push_back(tempValue); } currentTunedSpec.values.push_back(0.0); currentTunedSpec.values.push_back(0.0); currentTunedSpec.values.push_back(0.0); // upper edge vector<float> runningmean = SpecialConvolution(currentTunedSpec.values,hw); vector<float> runningstd; for (int i = 0; i < nNote; i++) { // first step: squared values into vector (variance) runningstd.push_back((currentTunedSpec.values[i] - runningmean[i]) * (currentTunedSpec.values[i] - runningmean[i])); } runningstd = SpecialConvolution(runningstd,hw); // second step convolve for (int i = 0; i < nNote; i++) { runningstd[i] = sqrt(runningstd[i]); // square root to finally have running std if (runningstd[i] > 0) { // currentTunedSpec.values[i] = (currentTunedSpec.values[i] / runningmean[i]) > thresh ? // (currentTunedSpec.values[i] - runningmean[i]) / pow(runningstd[i],m_whitening) : 0; currentTunedSpec.values[i] = (currentTunedSpec.values[i] - runningmean[i]) > 0 ? (currentTunedSpec.values[i] - runningmean[i]) / pow(runningstd[i],m_whitening) : 0; } if (currentTunedSpec.values[i] < 0) { cerr << "ERROR: negative value in logfreq spectrum" << endl; } } tunedSpec.push_back(currentTunedSpec); count++; } if (debug_on) cerr << "done." << endl; /** Semitone spectrum and chromagrams Semitone-spaced log-frequency spectrum derived from the tuned log-freq spectrum above. the spectrum is inferred using a non-negative least squares algorithm. Three different kinds of chromagram are calculated, "treble", "bass", and "both" (which means bass and treble stacked onto each other). **/ if (m_useNNLS == 0) { if (debug_on) cerr << "[Chordino Plugin] Mapping to semitone spectrum and chroma ... "; } else { if (debug_on) cerr << "[Chordino Plugin] Performing NNLS and mapping to chroma ... "; } vector<vector<double> > chordogram; vector<vector<int> > scoreChordogram; vector<float> chordchange = vector<float>(tunedSpec.size(),0); count = 0; FeatureList chromaList; bool clipwarned = false; for (FeatureList::iterator it = tunedSpec.begin(); it != tunedSpec.end(); ++it) { Feature currentTunedSpec = *it; // logfreq spectrum Feature currentChromas; // treble and bass chromagram currentChromas.hasTimestamp = true; currentChromas.timestamp = currentTunedSpec.timestamp; float b[nNote]; bool some_b_greater_zero = false; float sumb = 0; for (int i = 0; i < nNote; i++) { // b[i] = m_dict[(nNote * count + i) % (nNote * 84)]; b[i] = currentTunedSpec.values[i]; sumb += b[i]; if (b[i] > 0) { some_b_greater_zero = true; } } // here's where the non-negative least squares algorithm calculates the note activation x vector<float> chroma = vector<float>(12, 0); vector<float> basschroma = vector<float>(12, 0); float currval; int iSemitone = 0; if (some_b_greater_zero) { if (m_useNNLS == 0) { for (int iNote = nBPS/2 + 2; iNote < nNote - nBPS/2; iNote += nBPS) { currval = 0; for (int iBPS = -nBPS/2; iBPS < nBPS/2+1; ++iBPS) { currval += b[iNote + iBPS] * (1-abs(iBPS*1.0/(nBPS/2+1))); } chroma[iSemitone % 12] += currval * treblewindow[iSemitone]; basschroma[iSemitone % 12] += currval * basswindow[iSemitone]; iSemitone++; } } else { float x[84+1000]; for (int i = 1; i < 1084; ++i) x[i] = 1.0; vector<int> signifIndex; int index=0; sumb /= 84.0; for (int iNote = nBPS/2 + 2; iNote < nNote - nBPS/2; iNote += nBPS) { float currval = 0; for (int iBPS = -nBPS/2; iBPS < nBPS/2+1; ++iBPS) { currval += b[iNote + iBPS]; } if (currval > 0) signifIndex.push_back(index); index++; } float rnorm; float w[84+1000]; float zz[84+1000]; int indx[84+1000]; int mode; int dictsize = nNote*signifIndex.size(); // cerr << "dictsize is " << dictsize << "and values size" << f3.values.size()<< endl; float *curr_dict = new float[dictsize]; for (int iNote = 0; iNote < (int)signifIndex.size(); ++iNote) { for (int iBin = 0; iBin < nNote; iBin++) { curr_dict[iNote * nNote + iBin] = 1.0 * m_dict[signifIndex[iNote] * nNote + iBin]; } } nnls(curr_dict, nNote, nNote, signifIndex.size(), b, x, &rnorm, w, zz, indx, &mode); delete [] curr_dict; for (int iNote = 0; iNote < (int)signifIndex.size(); ++iNote) { // cerr << mode << endl; chroma[signifIndex[iNote] % 12] += x[iNote] * treblewindow[signifIndex[iNote]]; basschroma[signifIndex[iNote] % 12] += x[iNote] * basswindow[signifIndex[iNote]]; } } } vector<float> origchroma = chroma; chroma.insert(chroma.begin(), basschroma.begin(), basschroma.end()); // just stack the both chromas currentChromas.values = chroma; if (m_doNormalizeChroma > 0) { vector<float> chromanorm = vector<float>(3,0); switch (int(m_doNormalizeChroma)) { case 0: // should never end up here break; case 1: chromanorm[0] = *max_element(origchroma.begin(), origchroma.end()); chromanorm[1] = *max_element(basschroma.begin(), basschroma.end()); chromanorm[2] = max(chromanorm[0], chromanorm[1]); break; case 2: for (vector<float>::iterator it = chroma.begin(); it != chroma.end(); ++it) { chromanorm[2] += *it; } break; case 3: for (vector<float>::iterator it = chroma.begin(); it != chroma.end(); ++it) { chromanorm[2] += pow(*it,2); } chromanorm[2] = sqrt(chromanorm[2]); break; } if (chromanorm[2] > 0) { for (int i = 0; i < (int)chroma.size(); i++) { currentChromas.values[i] /= chromanorm[2]; } } } chromaList.push_back(currentChromas); // local chord estimation vector<double> currentChordSalience; double tempchordvalue = 0; double sumchordvalue = 0; for (int iChord = 0; iChord < nChord; iChord++) { tempchordvalue = 0; for (int iBin = 0; iBin < 12; iBin++) { tempchordvalue += m_chorddict[24 * iChord + iBin] * chroma[iBin]; } for (int iBin = 12; iBin < 24; iBin++) { tempchordvalue += m_chorddict[24 * iChord + iBin] * chroma[iBin]; } if (iChord == nChord-1) tempchordvalue *= .7; if (tempchordvalue < 0) tempchordvalue = 0.0; if (tempchordvalue > 200.0) { if (!clipwarned) { cerr << "WARNING: interim chroma contains extreme chord value " << tempchordvalue << ", clipping this and any others that appear" << endl; clipwarned = true; } tempchordvalue = 200.0; } tempchordvalue = pow(1.3, tempchordvalue); sumchordvalue += tempchordvalue; currentChordSalience.push_back(tempchordvalue); } if (sumchordvalue > 0) { for (int iChord = 0; iChord < nChord; iChord++) { currentChordSalience[iChord] /= sumchordvalue; } } else { currentChordSalience[nChord-1] = 1.0; } chordogram.push_back(currentChordSalience); count++; } if (debug_on) cerr << "done." << endl; vector<Feature> oldnotes; if (debug_on) cerr << "[Chordino Plugin] HMM Chord Estimation ... "; int oldchord = nChord-1; double selftransprob = 0.99; // vector<double> init = vector<double>(nChord,1.0/nChord); vector<double> init = vector<double>(nChord,0); init[nChord-1] = 1; double *delta; delta = (double *)malloc(sizeof(double)*nFrame*nChord); vector<vector<double> > trans; for (int iChord = 0; iChord < nChord; iChord++) { vector<double> temp = vector<double>(nChord,(1-selftransprob)/(nChord-1)); temp[iChord] = selftransprob; trans.push_back(temp); } vector<double> scale; vector<int> chordpath = ViterbiPath(init, trans, chordogram, delta, &scale); Feature chord_feature; // chord estimate chord_feature.hasTimestamp = true; chord_feature.timestamp = timestamps[0]; chord_feature.label = m_chordnames[chordpath[0]]; fsOut[m_outputChords].push_back(chord_feature); chordchange[0] = 0; for (int iFrame = 1; iFrame < (int)chordpath.size(); ++iFrame) { if (chordpath[iFrame] != oldchord ) { // chord Feature chord_feature; // chord estimate chord_feature.hasTimestamp = true; chord_feature.timestamp = timestamps[iFrame]; chord_feature.label = m_chordnames[chordpath[iFrame]]; fsOut[m_outputChords].push_back(chord_feature); oldchord = chordpath[iFrame]; // chord notes for (int iNote = 0; iNote < (int)oldnotes.size(); ++iNote) { // finish duration of old chord oldnotes[iNote].duration = oldnotes[iNote].duration + timestamps[iFrame]; fsOut[m_outputChordnotes].push_back(oldnotes[iNote]); } oldnotes.clear(); for (int iNote = 0; iNote < (int)m_chordnotes[chordpath[iFrame]].size(); ++iNote) { // prepare notes of current chord Feature chordnote_feature; chordnote_feature.hasTimestamp = true; chordnote_feature.timestamp = timestamps[iFrame]; chordnote_feature.values.push_back(m_chordnotes[chordpath[iFrame]][iNote]); chordnote_feature.hasDuration = true; chordnote_feature.duration = -timestamps[iFrame]; // this will be corrected at the next chord oldnotes.push_back(chordnote_feature); } } /* calculating simple chord change prob */ for (int iChord = 0; iChord < nChord; iChord++) { double num = delta[(iFrame-1) * nChord + iChord]; double denom = delta[iFrame * nChord + iChord]; double eps = 1e-7; if (denom < eps) denom = eps; chordchange[iFrame-1] += num * log(num / denom + eps); } } float logscale = 0; for (int iFrame = 0; iFrame < nFrame; ++iFrame) { logscale -= log(scale[iFrame]); Feature loglikelihood; loglikelihood.hasTimestamp = true; loglikelihood.timestamp = timestamps[iFrame]; loglikelihood.values.push_back(-log(scale[iFrame])); // cerr << chordchange[iFrame] << endl; fsOut[m_outputLoglikelihood].push_back(loglikelihood); } logscale /= nFrame; chord_feature.hasTimestamp = true; chord_feature.timestamp = timestamps[timestamps.size()-1]; chord_feature.label = "N"; fsOut[m_outputChords].push_back(chord_feature); for (int iNote = 0; iNote < (int)oldnotes.size(); ++iNote) { // finish duration of old chord oldnotes[iNote].duration = oldnotes[iNote].duration + timestamps[timestamps.size()-1]; fsOut[m_outputChordnotes].push_back(oldnotes[iNote]); } if (debug_on) cerr << "done." << endl; for (int iFrame = 0; iFrame < nFrame; iFrame++) { Feature chordchange_feature; chordchange_feature.hasTimestamp = true; chordchange_feature.timestamp = timestamps[iFrame]; chordchange_feature.values.push_back(chordchange[iFrame]); // cerr << "putting value " << chordchange[iFrame] << " at time " << chordchange_feature.timestamp << endl; fsOut[m_outputHarmonicChange].push_back(chordchange_feature); } free(delta); // for (int iFrame = 0; iFrame < nFrame; iFrame++) cerr << fsOut[m_outputHarmonicChange][iFrame].values[0] << endl; return fsOut; }